reactos/rosapps/applications/net/ncftp/libncftp/io.c
Aleksey Bragin 2012315e5a - Start rosapps rearrange and cleanup process.
svn path=/trunk/; revision=34303
2008-07-05 11:46:22 +00:00

2825 lines
71 KiB
C

/* io.c
*
* Copyright (c) 1996-2001 Mike Gleason, NCEMRSoft.
* All rights reserved.
*
*/
#include "syshdrs.h"
static int gGotBrokenData = 0;
#if defined(WIN32) || defined(_WINDOWS)
# define ASCII_TRANSLATION 0
#endif
#ifndef ASCII_TRANSLATION
# define ASCII_TRANSLATION 1
#endif
#if !defined(NO_SIGNALS) && (USE_SIO || !defined(SIGALRM) || !defined(SIGPIPE) || !defined(SIGINT))
# define NO_SIGNALS 1
#endif
#ifndef NO_SIGNALS
#ifdef HAVE_SIGSETJMP
static sigjmp_buf gBrokenDataJmp;
#else
static jmp_buf gBrokenDataJmp;
#endif /* HAVE_SIGSETJMP */
static int gCanBrokenDataJmp = 0;
#endif /* NO_SIGNALS */
#ifndef O_BINARY
/* Needed for platforms using different EOLN sequence (i.e. DOS) */
# ifdef _O_BINARY
# define O_BINARY _O_BINARY
# else
# define O_BINARY 0
# endif
#endif
static int WaitForRemoteInput(const FTPCIPtr cip);
static int WaitForRemoteOutput(const FTPCIPtr cip);
#ifndef NO_SIGNALS
static void
BrokenData(int signum)
{
gGotBrokenData = signum;
if (gCanBrokenDataJmp != 0) {
gCanBrokenDataJmp = 0;
#ifdef HAVE_SIGSETJMP
siglongjmp(gBrokenDataJmp, 1);
#else
longjmp(gBrokenDataJmp, 1);
#endif /* HAVE_SIGSETJMP */
}
} /* BrokenData */
#endif /* NO_SIGNALS */
void
FTPInitIOTimer(const FTPCIPtr cip)
{
cip->bytesTransferred = (longest_int) 0;
cip->expectedSize = kSizeUnknown;
cip->mdtm = kModTimeUnknown;
cip->rname = NULL;
cip->lname = NULL;
cip->kBytesPerSec = -1.0;
cip->percentCompleted = -1.0;
cip->sec = -1.0;
cip->secLeft = -1.0;
cip->nextProgressUpdate = 0;
cip->stalled = 0;
cip->dataTimedOut = 0;
cip->useProgressMeter = 1;
(void) gettimeofday(&cip->t0, NULL);
} /* FTPInitIOTimer */
void
FTPStartIOTimer(const FTPCIPtr cip)
{
(void) gettimeofday(&cip->t0, NULL);
if (cip->progress != (FTPProgressMeterProc) 0)
(*cip->progress)(cip, kPrInitMsg);
} /* FTPStartIOTimer */
void
FTPUpdateIOTimer(const FTPCIPtr cip)
{
double sec;
struct timeval *t0, t1;
time_t now;
(void) time(&now);
if (now < cip->nextProgressUpdate)
return;
now += 1;
cip->nextProgressUpdate = now;
(void) gettimeofday(&t1, NULL);
t0 = &cip->t0;
if (t0->tv_usec > t1.tv_usec) {
t1.tv_usec += 1000000;
t1.tv_sec--;
}
sec = ((double) (t1.tv_usec - t0->tv_usec) * 0.000001)
+ (t1.tv_sec - t0->tv_sec);
if (sec > 0.0) {
cip->kBytesPerSec = ((double) cip->bytesTransferred) / (1024.0 * sec);
} else {
cip->kBytesPerSec = -1.0;
}
if (cip->expectedSize == kSizeUnknown) {
cip->percentCompleted = -1.0;
cip->secLeft = -1.0;
} else if (cip->expectedSize <= 0) {
cip->percentCompleted = 100.0;
cip->secLeft = 0.0;
} else {
cip->percentCompleted = ((double) (100.0 * (cip->bytesTransferred + cip->startPoint))) / ((double) cip->expectedSize);
if (cip->percentCompleted >= 100.0) {
cip->percentCompleted = 100.0;
cip->secLeft = 0.0;
} else if (cip->percentCompleted <= 0.0) {
cip->secLeft = 999.0;
}
if (cip->kBytesPerSec > 0.0) {
cip->secLeft = ((cip->expectedSize - cip->bytesTransferred - cip->startPoint) / 1024.0) / cip->kBytesPerSec;
if (cip->secLeft < 0.0)
cip->secLeft = 0.0;
}
}
cip->sec = sec;
if ((cip->progress != (FTPProgressMeterProc) 0) && (cip->useProgressMeter != 0))
(*cip->progress)(cip, kPrUpdateMsg);
} /* FTPUpdateIOTimer */
void
FTPStopIOTimer(const FTPCIPtr cip)
{
cip->nextProgressUpdate = 0; /* force last update */
FTPUpdateIOTimer(cip);
if (cip->progress != (FTPProgressMeterProc) 0)
(*cip->progress)(cip, kPrEndMsg);
} /* FTPStopIOTimer */
/* This isn't too useful -- it mostly serves as an example so you can write
* your own function to do what you need to do with the listing.
*/
int
FTPList(const FTPCIPtr cip, const int outfd, const int longMode, const char *const lsflag)
{
const char *cmd;
char line[512];
char secondaryBuf[768];
#ifndef NO_SIGNALS
char *secBufPtr, *secBufLimit;
int nread;
volatile int result;
#else /* NO_SIGNALS */
SReadlineInfo lsSrl;
int result;
#endif /* NO_SIGNALS */
if (cip == NULL)
return (kErrBadParameter);
if (strcmp(cip->magic, kLibraryMagic))
return (kErrBadMagic);
cmd = (longMode != 0) ? "LIST" : "NLST";
if ((lsflag == NULL) || (lsflag[0] == '\0')) {
result = FTPStartDataCmd(cip, kNetReading, kTypeAscii, (longest_int) 0, "%s", cmd);
} else {
result = FTPStartDataCmd(cip, kNetReading, kTypeAscii, (longest_int) 0, "%s %s", cmd, lsflag);
}
#ifdef NO_SIGNALS
if (result == 0) {
if (InitSReadlineInfo(&lsSrl, cip->dataSocket, secondaryBuf, sizeof(secondaryBuf), (int) cip->xferTimeout, 1) < 0) {
/* Not really fdopen, but close in what we're trying to do. */
result = kErrFdopenR;
cip->errNo = kErrFdopenR;
Error(cip, kDoPerror, "Could not fdopen.\n");
return (result);
}
for (;;) {
result = SReadline(&lsSrl, line, sizeof(line) - 2);
if (result == kTimeoutErr) {
/* timeout */
Error(cip, kDontPerror, "Could not directory listing data -- timed out.\n");
cip->errNo = kErrDataTimedOut;
return (cip->errNo);
} else if (result == 0) {
/* end of listing -- done */
cip->numListings++;
break;
} else if (result < 0) {
/* error */
Error(cip, kDoPerror, "Could not read directory listing data");
result = kErrLISTFailed;
cip->errNo = kErrLISTFailed;
break;
}
(void) write(outfd, line, strlen(line));
}
DisposeSReadlineInfo(&lsSrl);
if (FTPEndDataCmd(cip, 1) < 0) {
result = kErrLISTFailed;
cip->errNo = kErrLISTFailed;
}
} else if (result == kErrGeneric) {
result = kErrLISTFailed;
cip->errNo = kErrLISTFailed;
}
#else /* NO_SIGNALS */
if (result == 0) {
/* This line sets the buffer pointer so that the first thing
* BufferGets will do is reset and fill the buffer using
* real I/O.
*/
secBufPtr = secondaryBuf + sizeof(secondaryBuf);
secBufLimit = (char *) 0;
for (;;) {
if (cip->xferTimeout > 0)
(void) alarm(cip->xferTimeout);
nread = BufferGets(line, sizeof(line), cip->dataSocket, secondaryBuf, &secBufPtr, &secBufLimit, sizeof(secondaryBuf));
if (nread <= 0) {
if (nread < 0)
break;
} else {
cip->bytesTransferred += (longest_int) nread;
(void) STRNCAT(line, "\n");
(void) write(outfd, line, strlen(line));
}
}
if (cip->xferTimeout > 0)
(void) alarm(0);
result = FTPEndDataCmd(cip, 1);
if (result < 0) {
result = kErrLISTFailed;
cip->errNo = kErrLISTFailed;
}
result = kNoErr;
cip->numListings++;
} else if (result == kErrGeneric) {
result = kErrLISTFailed;
cip->errNo = kErrLISTFailed;
}
#endif /* NO_SIGNALS */
return (result);
} /* FTPList */
static void
FTPRequestMlsOptions(const FTPCIPtr cip)
{
int f;
char optstr[128];
size_t optstrlen;
if (cip->usedMLS == 0) {
/* First MLSD/MLST ? */
cip->usedMLS = 1;
f = cip->mlsFeatures & kPreferredMlsOpts;
optstr[0] = '\0';
/* TYPE */
if ((f & kMlsOptType) != 0) {
STRNCAT(optstr, "type;");
}
/* SIZE */
if ((f & kMlsOptSize) != 0) {
STRNCAT(optstr, "size;");
}
/* MODTIME */
if ((f & kMlsOptModify) != 0) {
STRNCAT(optstr, "modify;");
}
/* MODE */
if ((f & kMlsOptUNIXmode) != 0) {
STRNCAT(optstr, "UNIX.mode;");
}
/* PERM */
if ((f & kMlsOptPerm) != 0) {
STRNCAT(optstr, "perm;");
}
/* OWNER */
if ((f & kMlsOptUNIXowner) != 0) {
STRNCAT(optstr, "UNIX.owner;");
}
/* UID */
if ((f & kMlsOptUNIXuid) != 0) {
STRNCAT(optstr, "UNIX.uid;");
}
/* GROUP */
if ((f & kMlsOptUNIXgroup) != 0) {
STRNCAT(optstr, "UNIX.group;");
}
/* GID */
if ((f & kMlsOptUNIXgid) != 0) {
STRNCAT(optstr, "UNIX.gid;");
}
/* UNIQUE */
if ((f & kMlsOptUnique) != 0) {
STRNCAT(optstr, "unique;");
}
/* Tell the server what we prefer. */
optstrlen = strlen(optstr);
if (optstrlen > 0) {
if (optstr[optstrlen - 1] == ';')
optstr[optstrlen - 1] = '\0';
(void) FTPCmd(cip, "OPTS MLST %s", optstr);
}
}
} /* FTPRequestMlsOptions */
int
FTPListToMemory2(const FTPCIPtr cip, const char *const pattern, const LineListPtr llines, const char *const lsflags, const int blankLines, int *const tryMLSD)
{
char secondaryBuf[768];
char line[512];
char lsflags1[128];
const char *command = "NLST";
const char *scp;
char *dcp, *lim;
#ifndef NO_SIGNALS
char *secBufPtr, *secBufLimit;
volatile FTPSigProc osigpipe;
volatile FTPCIPtr vcip;
int sj;
int nread;
volatile int result;
#else /* NO_SIGNALS */
SReadlineInfo lsSrl;
int result;
#endif /* NO_SIGNALS */
if (cip == NULL)
return (kErrBadParameter);
if (strcmp(cip->magic, kLibraryMagic))
return (kErrBadMagic);
if ((llines == NULL) || (pattern == NULL) || (lsflags == NULL))
return (kErrBadParameter);
if ((tryMLSD != (int *) 0) && (*tryMLSD != 0) && (cip->hasMLSD == kCommandAvailable)) {
command = "MLSD";
if ((lsflags[0] == '-') && (strchr(lsflags, 'd') != NULL) && (cip->hasMLST == kCommandAvailable))
command = "MLST";
lsflags1[0] = '\0';
FTPRequestMlsOptions(cip);
} else {
/* Not using MLSD. */
if (tryMLSD != (int *) 0)
*tryMLSD = 0;
if (lsflags[0] == '-') {
/* See if we should use LIST instead. */
scp = lsflags + 1;
dcp = lsflags1;
lim = lsflags1 + sizeof(lsflags1) - 2;
for (; *scp != '\0'; scp++) {
if (*scp == 'l') {
/* do not add the 'l' */
command = "LIST";
} else if (dcp < lim) {
if (dcp == lsflags1)
*dcp++ = '-';
*dcp++ = *scp;
}
}
*dcp = '\0';
} else {
(void) STRNCPY(lsflags1, lsflags);
}
}
InitLineList(llines);
result = FTPStartDataCmd(
cip,
kNetReading,
kTypeAscii,
(longest_int) 0,
"%s%s%s%s%s",
command,
(lsflags1[0] == '\0') ? "" : " ",
lsflags1,
(pattern[0] == '\0') ? "" : " ",
pattern
);
#ifdef NO_SIGNALS
if (result == 0) {
if (InitSReadlineInfo(&lsSrl, cip->dataSocket, secondaryBuf, sizeof(secondaryBuf), (int) cip->xferTimeout, 1) < 0) {
/* Not really fdopen, but close in what we're trying to do. */
result = kErrFdopenR;
cip->errNo = kErrFdopenR;
Error(cip, kDoPerror, "Could not fdopen.\n");
return (result);
}
for (;;) {
result = SReadline(&lsSrl, line, sizeof(line) - 1);
if (result == kTimeoutErr) {
/* timeout */
Error(cip, kDontPerror, "Could not directory listing data -- timed out.\n");
cip->errNo = kErrDataTimedOut;
return (cip->errNo);
} else if (result == 0) {
/* end of listing -- done */
cip->numListings++;
break;
} else if (result < 0) {
/* error */
Error(cip, kDoPerror, "Could not read directory listing data");
result = kErrLISTFailed;
cip->errNo = kErrLISTFailed;
break;
}
if (line[result - 1] == '\n')
line[result - 1] = '\0';
if ((blankLines == 0) && (result <= 1))
continue;
/* Valid directory listing line of output */
if ((line[0] == '.') && ((line[1] == '\0') || ((line[1] == '.') && ((line[2] == '\0') || (iscntrl(line[2]))))))
continue; /* Skip . and .. */
(void) AddLine(llines, line);
}
DisposeSReadlineInfo(&lsSrl);
if (FTPEndDataCmd(cip, 1) < 0) {
result = kErrLISTFailed;
cip->errNo = kErrLISTFailed;
}
} else if (result == kErrGeneric) {
result = kErrLISTFailed;
cip->errNo = kErrLISTFailed;
}
#else /* NO_SIGNALS */
vcip = cip;
osigpipe = (volatile FTPSigProc) signal(SIGPIPE, BrokenData);
gGotBrokenData = 0;
gCanBrokenDataJmp = 0;
#ifdef HAVE_SIGSETJMP
sj = sigsetjmp(gBrokenDataJmp, 1);
#else
sj = setjmp(gBrokenDataJmp);
#endif /* HAVE_SIGSETJMP */
if (sj != 0) {
(void) signal(SIGPIPE, (FTPSigProc) osigpipe);
FTPShutdownHost(vcip);
vcip->errNo = kErrRemoteHostClosedConnection;
return(vcip->errNo);
}
gCanBrokenDataJmp = 1;
if (result == 0) {
/* This line sets the buffer pointer so that the first thing
* BufferGets will do is reset and fill the buffer using
* real I/O.
*/
secBufPtr = secondaryBuf + sizeof(secondaryBuf);
secBufLimit = (char *) 0;
memset(secondaryBuf, 0, sizeof(secondaryBuf));
for (;;) {
memset(line, 0, sizeof(line));
if (cip->xferTimeout > 0)
(void) alarm(cip->xferTimeout);
nread = BufferGets(line, sizeof(line), cip->dataSocket, secondaryBuf, &secBufPtr, &secBufLimit, sizeof(secondaryBuf));
if (nread <= 0) {
if (nread < 0)
break;
if (blankLines != 0)
(void) AddLine(llines, line);
} else {
cip->bytesTransferred += (longest_int) nread;
if ((line[0] == '.') && ((line[1] == '\0') || ((line[1] == '.') && ((line[2] == '\0') || (iscntrl(line[2]))))))
continue; /* Skip . and .. */
(void) AddLine(llines, line);
}
}
if (cip->xferTimeout > 0)
(void) alarm(0);
result = FTPEndDataCmd(cip, 1);
if (result < 0) {
result = kErrLISTFailed;
cip->errNo = kErrLISTFailed;
}
result = kNoErr;
cip->numListings++;
} else if (result == kErrGeneric) {
result = kErrLISTFailed;
cip->errNo = kErrLISTFailed;
}
(void) signal(SIGPIPE, (FTPSigProc) osigpipe);
#endif /* NO_SIGNALS */
return (result);
} /* FTPListToMemory2 */
static void
AutomaticallyUseASCIIModeDependingOnExtension(const FTPCIPtr cip, const char *const pathName, int *const xtype)
{
if ((*xtype == kTypeBinary) && (cip->asciiFilenameExtensions != NULL)) {
if (FilenameExtensionIndicatesASCII(pathName, cip->asciiFilenameExtensions)) {
/* Matched -- send this file in ASCII mode
* instead of binary since it's extension
* appears to be that of a text file.
*/
*xtype = kTypeAscii;
}
}
} /* AutomaticallyUseASCIIModeDependingOnExtension */
/* The purpose of this is to provide updates for the progress meters
* during lags. Return zero if the operation timed-out.
*/
static int
WaitForRemoteOutput(const FTPCIPtr cip)
{
fd_set ss, ss2;
struct timeval tv;
int result;
int fd;
int wsecs;
int xferTimeout;
int ocancelXfer;
xferTimeout = cip->xferTimeout;
if (xferTimeout < 1)
return (1);
fd = cip->dataSocket;
if (fd < 0)
return (1);
ocancelXfer = cip->cancelXfer;
wsecs = 0;
cip->stalled = 0;
while ((xferTimeout <= 0) || (wsecs < xferTimeout)) {
if ((cip->cancelXfer != 0) && (ocancelXfer == 0)) {
/* leave cip->stalled -- could have been stalled and then canceled. */
return (1);
}
FD_ZERO(&ss);
FD_SET(fd, &ss);
ss2 = ss;
tv.tv_sec = 1;
tv.tv_usec = 0;
result = select(fd + 1, NULL, SELECT_TYPE_ARG234 &ss, SELECT_TYPE_ARG234 &ss2, &tv);
if (result == 1) {
/* ready */
cip->stalled = 0;
return (1);
} else if (result < 0) {
if (errno != EINTR) {
perror("select");
cip->stalled = 0;
return (1);
}
} else {
wsecs++;
cip->stalled = wsecs;
}
FTPUpdateIOTimer(cip);
}
#if !defined(NO_SIGNALS)
/* Shouldn't get here -- alarm() should have
* went off by now.
*/
(void) kill(getpid(), SIGALRM);
#endif /* NO_SIGNALS */
cip->dataTimedOut = 1;
return (0); /* timed-out */
} /* WaitForRemoteOutput */
static int
FTPPutOneF(
const FTPCIPtr cip,
const char *const file,
const char *volatile dstfile,
int xtype,
const int fdtouse,
const int appendflag,
const char *volatile tmppfx,
const char *volatile tmpsfx,
const int resumeflag,
const int deleteflag,
const ConfirmResumeUploadProc resumeProc)
{
char *buf, *cp;
const char *cmd;
const char *odstfile;
size_t bufSize;
size_t l;
int tmpResult, result;
int nread, nwrote;
volatile int fd;
char dstfile2[512];
#if ASCII_TRANSLATION
char *src, *srclim, *dst;
int ntowrite;
char inbuf[256];
#endif
int fstatrc, statrc;
longest_int startPoint = 0;
struct Stat st;
time_t mdtm;
#if !defined(NO_SIGNALS)
int sj;
volatile FTPSigProc osigpipe;
volatile FTPCIPtr vcip;
volatile int vfd, vfdtouse;
#endif /* NO_SIGNALS */
volatile int vzaction;
int zaction = kConfirmResumeProcSaidBestGuess;
if (cip->buf == NULL) {
Error(cip, kDoPerror, "Transfer buffer not allocated.\n");
cip->errNo = kErrNoBuf;
return (cip->errNo);
}
cip->usingTAR = 0;
if (fdtouse < 0) {
fd = Open(file, O_RDONLY|O_BINARY, 0);
if (fd < 0) {
Error(cip, kDoPerror, "Cannot open local file %s for reading.\n", file);
cip->errNo = kErrOpenFailed;
return (cip->errNo);
}
} else {
fd = fdtouse;
}
fstatrc = Fstat(fd, &st);
if ((fstatrc == 0) && (S_ISDIR(st.st_mode))) {
if (fdtouse < 0) {
(void) close(fd);
}
Error(cip, kDontPerror, "%s is a directory.\n", (file != NULL) ? file : "that");
cip->errNo = kErrOpenFailed;
return (cip->errNo);
}
/* For Put, we can't recover very well if it turns out restart
* didn't work, so check beforehand.
*/
if (cip->hasREST == kCommandAvailabilityUnknown) {
(void) FTPSetTransferType(cip, kTypeBinary);
if (SetStartOffset(cip, (longest_int) 1) == kNoErr) {
/* Now revert -- we still may not end up
* doing it.
*/
SetStartOffset(cip, (longest_int) -1);
}
}
if (fdtouse < 0) {
AutomaticallyUseASCIIModeDependingOnExtension(cip, dstfile, &xtype);
(void) FTPFileSizeAndModificationTime(cip, dstfile, &startPoint, xtype, &mdtm);
if (appendflag == kAppendYes) {
zaction = kConfirmResumeProcSaidAppend;
} else if (
(cip->hasREST == kCommandNotAvailable) ||
(xtype != kTypeBinary) ||
(fstatrc < 0)
) {
zaction = kConfirmResumeProcSaidOverwrite;
} else if (resumeflag == kResumeYes) {
zaction = kConfirmResumeProcSaidBestGuess;
} else {
zaction = kConfirmResumeProcSaidOverwrite;
}
statrc = -1;
if ((mdtm != kModTimeUnknown) || (startPoint != kSizeUnknown)) {
/* Then we know the file exists. We will
* ask the user what to do, if possible, below.
*/
statrc = 0;
} else if ((resumeProc != NoConfirmResumeUploadProc) && (cip->hasMDTM != kCommandAvailable) && (cip->hasSIZE != kCommandAvailable)) {
/* We already checked if the file had a filesize
* or timestamp above, but if the server indicated
* it did not support querying those directly,
* we now need to try to determine if the file
* exists in a few other ways.
*/
statrc = FTPFileExists2(cip, dstfile, 0, 0, 0, 1, 1);
}
if (
(resumeProc != NoConfirmResumeUploadProc) &&
(statrc == 0)
) {
zaction = (*resumeProc)(file, (longest_int) st.st_size, st.st_mtime, &dstfile, startPoint, mdtm, &startPoint);
}
if (zaction == kConfirmResumeProcSaidCancel) {
/* User wants to cancel this file and any
* remaining in batch.
*/
cip->errNo = kErrUserCanceled;
return (cip->errNo);
}
if (zaction == kConfirmResumeProcSaidBestGuess) {
if ((mdtm != kModTimeUnknown) && (st.st_mtime > (mdtm + 1))) {
/* Local file is newer than remote,
* overwrite the remote file instead
* of trying to resume it.
*
* Note: Add one second fudge factor
* for Windows' file timestamps being
* imprecise to one second.
*/
zaction = kConfirmResumeProcSaidOverwrite;
} else if ((longest_int) st.st_size == startPoint) {
/* Already sent file, done. */
zaction = kConfirmResumeProcSaidSkip;
} else if ((startPoint != kSizeUnknown) && ((longest_int) st.st_size > startPoint)) {
zaction = kConfirmResumeProcSaidResume;
} else {
zaction = kConfirmResumeProcSaidOverwrite;
}
}
if (zaction == kConfirmResumeProcSaidSkip) {
/* Nothing done, but not an error. */
if (fdtouse < 0) {
(void) close(fd);
}
if (deleteflag == kDeleteYes) {
if (unlink(file) < 0) {
cip->errNo = kErrLocalDeleteFailed;
return (cip->errNo);
}
}
return (kNoErr);
} else if (zaction == kConfirmResumeProcSaidResume) {
/* Resume; proc set the startPoint. */
if ((longest_int) st.st_size == startPoint) {
/* Already sent file, done. */
if (fdtouse < 0) {
(void) close(fd);
}
if (deleteflag == kDeleteYes) {
if (unlink(file) < 0) {
cip->errNo = kErrLocalDeleteFailed;
return (cip->errNo);
}
}
return (kNoErr);
} else if (Lseek(fd, (off_t) startPoint, SEEK_SET) != (off_t) -1) {
cip->startPoint = startPoint;
}
} else if (zaction == kConfirmResumeProcSaidAppend) {
/* append: leave startPoint at zero, we will append everything. */
cip->startPoint = startPoint = 0;
} else /* if (zaction == kConfirmResumeProcSaidOverwrite) */ {
/* overwrite: leave startPoint at zero */
cip->startPoint = startPoint = 0;
}
}
if ((cip->numUploads == 0) && (cip->dataSocketSBufSize > 0)) {
/* If dataSocketSBufSize is non-zero, it means you
* want to explicitly try to set the size of the
* socket's I/O buffer.
*
* If it is zero, it means you want to just use the
* TCP stack's default value, which is typically
* between 8 and 64 kB.
*
* If you try to set the buffer larger than 64 kB,
* the TCP stack should try to use RFC 1323 to
* negotiate "TCP Large Windows" which may yield
* significant performance gains.
*/
if (cip->hasSTORBUFSIZE == kCommandAvailable)
(void) FTPCmd(cip, "SITE STORBUFSIZE %lu", (unsigned long) cip->dataSocketSBufSize);
else if (cip->hasSBUFSIZ == kCommandAvailable)
(void) FTPCmd(cip, "SITE SBUFSIZ %lu", (unsigned long) cip->dataSocketSBufSize);
else if (cip->hasSBUFSZ == kCommandAvailable)
(void) FTPCmd(cip, "SITE SBUFSZ %lu", (unsigned long) cip->dataSocketSBufSize);
/* At least one server implemenation has RBUFSZ but not
* SBUFSZ and instead uses RBUFSZ for both.
*/
else if ((cip->hasSBUFSZ != kCommandAvailable) && (cip->hasRBUFSZ == kCommandAvailable))
(void) FTPCmd(cip, "SITE RBUFSZ %lu", (unsigned long) cip->dataSocketSBufSize);
else if (cip->hasBUFSIZE == kCommandAvailable)
(void) FTPCmd(cip, "SITE BUFSIZE %lu", (unsigned long) cip->dataSocketSBufSize);
}
#ifdef NO_SIGNALS
vzaction = zaction;
#else /* NO_SIGNALS */
vcip = cip;
vfdtouse = fdtouse;
vfd = fd;
vzaction = zaction;
osigpipe = (volatile FTPSigProc) signal(SIGPIPE, BrokenData);
gGotBrokenData = 0;
gCanBrokenDataJmp = 0;
#ifdef HAVE_SIGSETJMP
sj = sigsetjmp(gBrokenDataJmp, 1);
#else
sj = setjmp(gBrokenDataJmp);
#endif /* HAVE_SIGSETJMP */
if (sj != 0) {
(void) signal(SIGPIPE, (FTPSigProc) osigpipe);
if (vfdtouse < 0) {
(void) close(vfd);
}
FTPShutdownHost(vcip);
vcip->errNo = kErrRemoteHostClosedConnection;
return(vcip->errNo);
}
gCanBrokenDataJmp = 1;
#endif /* NO_SIGNALS */
if (vzaction == kConfirmResumeProcSaidAppend) {
cmd = "APPE";
tmppfx = ""; /* Can't use that here. */
tmpsfx = "";
} else {
cmd = "STOR";
if (tmppfx == NULL)
tmppfx = "";
if (tmpsfx == NULL)
tmpsfx = "";
}
odstfile = dstfile;
if ((tmppfx[0] != '\0') || (tmpsfx[0] != '\0')) {
cp = strrchr(dstfile, '/');
if (cp == NULL)
cp = strrchr(dstfile, '\\');
if (cp == NULL) {
(void) STRNCPY(dstfile2, tmppfx);
(void) STRNCAT(dstfile2, dstfile);
(void) STRNCAT(dstfile2, tmpsfx);
} else {
cp++;
l = (size_t) (cp - dstfile);
(void) STRNCPY(dstfile2, dstfile);
dstfile2[l] = '\0'; /* Nuke stuff after / */
(void) STRNCAT(dstfile2, tmppfx);
(void) STRNCAT(dstfile2, cp);
(void) STRNCAT(dstfile2, tmpsfx);
}
dstfile = dstfile2;
}
tmpResult = FTPStartDataCmd(
cip,
kNetWriting,
xtype,
startPoint,
"%s %s",
cmd,
dstfile
);
if (tmpResult < 0) {
cip->errNo = tmpResult;
if (fdtouse < 0) {
(void) close(fd);
}
#if !defined(NO_SIGNALS)
(void) signal(SIGPIPE, (FTPSigProc) osigpipe);
#endif /* NO_SIGNALS */
return (cip->errNo);
}
if ((startPoint != 0) && (cip->startPoint == 0)) {
/* Remote could not or would not set the start offset
* to what we wanted.
*
* So now we have to undo our seek.
*/
if (Lseek(fd, (off_t) 0, SEEK_SET) != (off_t) 0) {
cip->errNo = kErrLseekFailed;
if (fdtouse < 0) {
(void) close(fd);
}
#if !defined(NO_SIGNALS)
(void) signal(SIGPIPE, (FTPSigProc) osigpipe);
#endif /* NO_SIGNALS */
return (cip->errNo);
}
startPoint = 0;
}
result = kNoErr;
buf = cip->buf;
bufSize = cip->bufSize;
FTPInitIOTimer(cip);
if ((fstatrc == 0) && (S_ISREG(st.st_mode) != 0)) {
cip->expectedSize = (longest_int) st.st_size;
cip->mdtm = st.st_mtime;
}
cip->lname = file; /* could be NULL */
cip->rname = odstfile;
if (fdtouse >= 0)
cip->useProgressMeter = 0;
FTPStartIOTimer(cip);
/* Note: On Windows, we don't have to do anything special
* for ASCII mode, since Net ASCII's end-of-line sequence
* corresponds to the same thing used for DOS/Windows.
*/
#if ASCII_TRANSLATION
if (xtype == kTypeAscii) {
/* ascii */
for (;;) {
#if !defined(NO_SIGNALS)
gCanBrokenDataJmp = 0;
#endif /* NO_SIGNALS */
nread = read(fd, inbuf, sizeof(inbuf));
if (nread < 0) {
if (errno == EINTR) {
continue;
} else {
result = kErrReadFailed;
cip->errNo = kErrReadFailed;
Error(cip, kDoPerror, "Local read failed.\n");
}
break;
} else if (nread == 0) {
break;
}
cip->bytesTransferred += (longest_int) nread;
#if !defined(NO_SIGNALS)
gCanBrokenDataJmp = 1;
#endif /* NO_SIGNALS */
src = inbuf;
srclim = src + nread;
dst = cip->buf; /* must be 2x sizeof inbuf or more. */
while (src < srclim) {
if (*src == '\n')
*dst++ = '\r';
*dst++ = *src++;
}
ntowrite = (size_t) (dst - cip->buf);
cp = cip->buf;
#if !defined(NO_SIGNALS)
if (cip->xferTimeout > 0)
(void) alarm(cip->xferTimeout);
#endif /* NO_SIGNALS */
do {
if (! WaitForRemoteOutput(cip)) { /* could set cancelXfer */
cip->errNo = result = kErrDataTimedOut;
Error(cip, kDontPerror, "Remote write timed out.\n");
goto brk;
}
if (cip->cancelXfer > 0) {
FTPAbortDataTransfer(cip);
result = cip->errNo = kErrDataTransferAborted;
goto brk;
}
#ifdef NO_SIGNALS
nwrote = SWrite(cip->dataSocket, cp, (size_t) ntowrite, (int) cip->xferTimeout, kNoFirstSelect);
if (nwrote < 0) {
if (nwrote == kTimeoutErr) {
cip->errNo = result = kErrDataTimedOut;
Error(cip, kDontPerror, "Remote write timed out.\n");
} else if ((gGotBrokenData != 0) || (errno == EPIPE)) {
cip->errNo = result = kErrSocketWriteFailed;
errno = EPIPE;
Error(cip, kDoPerror, "Lost data connection to remote host.\n");
} else if (errno == EINTR) {
continue;
} else {
cip->errNo = result = kErrSocketWriteFailed;
Error(cip, kDoPerror, "Remote write failed.\n");
}
(void) shutdown(cip->dataSocket, 2);
goto brk;
}
#else /* NO_SIGNALS */
nwrote = write(cip->dataSocket, cp, ntowrite);
if (nwrote < 0) {
if ((gGotBrokenData != 0) || (errno == EPIPE)) {
cip->errNo = result = kErrSocketWriteFailed;
errno = EPIPE;
Error(cip, kDoPerror, "Lost data connection to remote host.\n");
} else if (errno == EINTR) {
continue;
} else {
cip->errNo = result = kErrSocketWriteFailed;
Error(cip, kDoPerror, "Remote write failed.\n");
}
(void) shutdown(cip->dataSocket, 2);
goto brk;
}
#endif /* NO_SIGNALS */
cp += nwrote;
ntowrite -= nwrote;
} while (ntowrite > 0);
FTPUpdateIOTimer(cip);
}
} else
#endif /* ASCII_TRANSLATION */
{
/* binary */
for (;;) {
#if !defined(NO_SIGNALS)
gCanBrokenDataJmp = 0;
#endif /* NO_SIGNALS */
cp = buf;
nread = read(fd, cp, bufSize);
if (nread < 0) {
if (errno == EINTR) {
continue;
} else {
result = kErrReadFailed;
cip->errNo = kErrReadFailed;
Error(cip, kDoPerror, "Local read failed.\n");
}
break;
} else if (nread == 0) {
break;
}
cip->bytesTransferred += (longest_int) nread;
#if !defined(NO_SIGNALS)
gCanBrokenDataJmp = 1;
if (cip->xferTimeout > 0)
(void) alarm(cip->xferTimeout);
#endif /* NO_SIGNALS */
do {
if (! WaitForRemoteOutput(cip)) { /* could set cancelXfer */
cip->errNo = result = kErrDataTimedOut;
Error(cip, kDontPerror, "Remote write timed out.\n");
goto brk;
}
if (cip->cancelXfer > 0) {
FTPAbortDataTransfer(cip);
result = cip->errNo = kErrDataTransferAborted;
goto brk;
}
#ifdef NO_SIGNALS
nwrote = SWrite(cip->dataSocket, cp, (size_t) nread, (int) cip->xferTimeout, kNoFirstSelect);
if (nwrote < 0) {
if (nwrote == kTimeoutErr) {
cip->errNo = result = kErrDataTimedOut;
Error(cip, kDontPerror, "Remote write timed out.\n");
} else if ((gGotBrokenData != 0) || (errno == EPIPE)) {
cip->errNo = result = kErrSocketWriteFailed;
errno = EPIPE;
Error(cip, kDoPerror, "Lost data connection to remote host.\n");
} else if (errno == EINTR) {
continue;
} else {
cip->errNo = result = kErrSocketWriteFailed;
Error(cip, kDoPerror, "Remote write failed.\n");
}
(void) shutdown(cip->dataSocket, 2);
cip->dataSocket = -1;
goto brk;
}
#else /* NO_SIGNALS */
nwrote = write(cip->dataSocket, cp, nread);
if (nwrote < 0) {
if ((gGotBrokenData != 0) || (errno == EPIPE)) {
cip->errNo = result = kErrSocketWriteFailed;
errno = EPIPE;
Error(cip, kDoPerror, "Lost data connection to remote host.\n");
} else if (errno == EINTR) {
continue;
} else {
cip->errNo = result = kErrSocketWriteFailed;
Error(cip, kDoPerror, "Remote write failed.\n");
}
(void) shutdown(cip->dataSocket, 2);
cip->dataSocket = -1;
goto brk;
}
#endif /* NO_SIGNALS */
cp += nwrote;
nread -= nwrote;
} while (nread > 0);
FTPUpdateIOTimer(cip);
}
}
brk:
if (fdtouse < 0) {
(void) Fstat(fd, &st);
}
if (fdtouse < 0) {
if (shutdown(fd, 1) == 0) {
/* This looks very bizarre, since
* we will be checking the socket
* for readability here!
*
* The reason for this is that we
* want to be able to timeout a
* small put. So, we close the
* write end of the socket first,
* which tells the server we're
* done writing. We then wait
* for the server to close down
* the whole socket, which tells
* us that the file was completed.
*/
(void) WaitForRemoteInput(cip); /* Close could block. */
}
}
#if !defined(NO_SIGNALS)
gCanBrokenDataJmp = 0;
if (cip->xferTimeout > 0)
(void) alarm(0);
#endif /* NO_SIGNALS */
tmpResult = FTPEndDataCmd(cip, 1);
if ((tmpResult < 0) && (result == kNoErr)) {
cip->errNo = result = kErrSTORFailed;
}
FTPStopIOTimer(cip);
if (fdtouse < 0) {
/* If they gave us a descriptor (fdtouse >= 0),
* leave it open, otherwise we opened it, so
* we need to dispose of it.
*/
(void) close(fd);
fd = -1;
}
if (result == kNoErr) {
/* The store succeeded; If we were
* uploading to a temporary file,
* move the new file to the new name.
*/
cip->numUploads++;
if ((tmppfx[0] != '\0') || (tmpsfx[0] != '\0')) {
if ((result = FTPRename(cip, dstfile, odstfile)) < 0) {
/* May fail if file was already there,
* so delete the old one so we can move
* over it.
*/
if (FTPDelete(cip, odstfile, kRecursiveNo, kGlobNo) == kNoErr) {
result = FTPRename(cip, dstfile, odstfile);
if (result < 0) {
Error(cip, kDontPerror, "Could not rename %s to %s: %s.\n", dstfile, odstfile, FTPStrError(cip->errNo));
}
} else {
Error(cip, kDontPerror, "Could not delete old %s, so could not rename %s to that: %s\n", odstfile, dstfile, FTPStrError(cip->errNo));
}
}
}
if (FTPUtime(cip, odstfile, st.st_atime, st.st_mtime, st.st_ctime) != kNoErr) {
if (cip->errNo != kErrUTIMENotAvailable)
Error(cip, kDontPerror, "Could not preserve times for %s: %s.\n", odstfile, FTPStrError(cip->errNo));
}
if (deleteflag == kDeleteYes) {
if (unlink(file) < 0) {
result = cip->errNo = kErrLocalDeleteFailed;
}
}
}
#if !defined(NO_SIGNALS)
(void) signal(SIGPIPE, (FTPSigProc) osigpipe);
#endif /* NO_SIGNALS */
return (result);
} /* FTPPutOneF */
int
FTPPutOneFile3(
const FTPCIPtr cip,
const char *const file,
const char *const dstfile,
const int xtype,
const int fdtouse,
const int appendflag,
const char *const tmppfx,
const char *const tmpsfx,
const int resumeflag,
const int deleteflag,
const ConfirmResumeUploadProc resumeProc,
int UNUSED(reserved))
{
int result;
LIBNCFTP_USE_VAR(reserved);
if (cip == NULL)
return (kErrBadParameter);
if (strcmp(cip->magic, kLibraryMagic))
return (kErrBadMagic);
if ((dstfile == NULL) || (dstfile[0] == '\0'))
return (kErrBadParameter);
if (fdtouse < 0) {
if ((file == NULL) || (file[0] == '\0'))
return (kErrBadParameter);
}
result = FTPPutOneF(cip, file, dstfile, xtype, fdtouse, appendflag, tmppfx, tmpsfx, resumeflag, deleteflag, resumeProc);
return (result);
} /* FTPPutOneFile3 */
int
FTPPutFiles3(
const FTPCIPtr cip,
const char *const pattern,
const char *const dstdir1,
const int recurse,
const int doGlob,
const int xtype,
int appendflag,
const char *const tmppfx,
const char *const tmpsfx,
const int resumeflag,
const int deleteflag,
const ConfirmResumeUploadProc resumeProc,
int UNUSED(reserved))
{
LineList globList;
FileInfoList files;
FileInfoPtr filePtr;
int batchResult;
int result;
const char *dstdir;
char dstdir2[512];
LIBNCFTP_USE_VAR(reserved);
if (cip == NULL)
return (kErrBadParameter);
if (strcmp(cip->magic, kLibraryMagic))
return (kErrBadMagic);
if (dstdir1 == NULL) {
dstdir = NULL;
} else {
dstdir = STRNCPY(dstdir2, dstdir1);
StrRemoveTrailingLocalPathDelim(dstdir2);
}
(void) FTPLocalGlob(cip, &globList, pattern, doGlob);
if (recurse == kRecursiveYes) {
appendflag = kAppendNo;
(void) FTPLocalRecursiveFileList(cip, &globList, &files);
if (files.first == NULL) {
cip->errNo = kErrNoValidFilesSpecified;
return (kErrNoValidFilesSpecified);
}
(void) ComputeRNames(&files, dstdir, 0, 1);
} else {
(void) LineListToFileInfoList(&globList, &files);
(void) ComputeLNames(&files, NULL, NULL, 1);
(void) ComputeRNames(&files, dstdir, 0, 0);
}
DisposeLineListContents(&globList);
#if 0
for (filePtr = files.first; filePtr != NULL; filePtr = filePtr->next) {
PrintF(cip, " R=%s, L=%s, 2=%s, size=%d, mdtm=%u, type=%c\n",
filePtr->rname,
filePtr->lname,
filePtr->rlinkto ? filePtr->rlinkto : "",
filePtr->size,
(unsigned int) filePtr->mdtm,
filePtr->type
);
}
#endif
batchResult = kNoErr;
for (filePtr = files.first; filePtr != NULL; filePtr = filePtr->next) {
if (cip->connected == 0) {
if (batchResult == kNoErr)
batchResult = kErrRemoteHostClosedConnection;
break;
}
if (filePtr->type == 'd') {
/* mkdir */
StrRemoveTrailingLocalPathDelim(filePtr->rname);
result = FTPMkdir(cip, filePtr->rname, kRecursiveNo);
if (result != kNoErr)
batchResult = result;
#ifdef HAVE_SYMLINK
} else if (filePtr->type == 'l') {
/* symlink */
/* no RFC way to create the link, though. */
if ((filePtr->rlinkto != NULL) && (filePtr->rlinkto[0] != '\0'))
(void) FTPSymlink(cip, filePtr->rname, filePtr->rlinkto);
#endif
} else if (recurse != kRecursiveYes) {
result = FTPPutOneF(cip, filePtr->lname, filePtr->rname, xtype, -1, appendflag, tmppfx, tmpsfx, resumeflag, deleteflag, resumeProc);
if (files.nFileInfos == 1) {
if (result != kNoErr)
batchResult = result;
} else {
if ((result != kNoErr) && (result != kErrLocalFileNewer) && (result != kErrRemoteFileNewer) && (result != kErrLocalSameAsRemote))
batchResult = result;
}
if (result == kErrUserCanceled)
cip->cancelXfer = 1;
if (cip->cancelXfer > 0)
break;
} else {
result = FTPPutOneF(cip, filePtr->lname, filePtr->rname, xtype, -1, appendflag, tmppfx, tmpsfx, resumeflag, deleteflag, resumeProc);
if (files.nFileInfos == 1) {
if (result != kNoErr)
batchResult = result;
} else {
if ((result != kNoErr) && (result != kErrLocalFileNewer) && (result != kErrRemoteFileNewer) && (result != kErrLocalSameAsRemote))
batchResult = result;
}
if (result == kErrUserCanceled)
cip->cancelXfer = 1;
if (cip->cancelXfer > 0)
break;
}
}
DisposeFileInfoListContents(&files);
if (batchResult < 0)
cip->errNo = batchResult;
return (batchResult);
} /* FTPPutFiles3 */
/* The purpose of this is to provide updates for the progress meters
* during lags. Return zero if the operation timed-out.
*/
static int
WaitForRemoteInput(const FTPCIPtr cip)
{
fd_set ss, ss2;
struct timeval tv;
int result;
int fd;
int wsecs;
int xferTimeout;
int ocancelXfer;
xferTimeout = cip->xferTimeout;
if (xferTimeout < 1)
return (1);
fd = cip->dataSocket;
if (fd < 0)
return (1);
ocancelXfer = cip->cancelXfer;
wsecs = 0;
cip->stalled = 0;
while ((xferTimeout <= 0) || (wsecs < xferTimeout)) {
if ((cip->cancelXfer != 0) && (ocancelXfer == 0)) {
/* leave cip->stalled -- could have been stalled and then canceled. */
return (1);
}
FD_ZERO(&ss);
FD_SET(fd, &ss);
ss2 = ss;
tv.tv_sec = 1;
tv.tv_usec = 0;
result = select(fd + 1, SELECT_TYPE_ARG234 &ss, NULL, SELECT_TYPE_ARG234 &ss2, &tv);
if (result == 1) {
/* ready */
cip->stalled = 0;
return (1);
} else if (result < 0) {
if (result != EINTR) {
perror("select");
cip->stalled = 0;
return (1);
}
} else {
wsecs++;
cip->stalled = wsecs;
}
FTPUpdateIOTimer(cip);
}
#if !defined(NO_SIGNALS)
/* Shouldn't get here -- alarm() should have
* went off by now.
*/
(void) kill(getpid(), SIGALRM);
#endif /* NO_SIGNALS */
cip->dataTimedOut = 1;
return (0); /* timed-out */
} /* WaitForRemoteInput */
/* Nice for UNIX, but not necessary otherwise. */
#ifdef TAR
static int
OpenTar(const FTPCIPtr cip, const char *const dstdir, int *const pid)
{
int pipe1[2];
int pid1;
int i;
char *argv[8];
*pid = -1;
if (access(TAR, X_OK) < 0) {
/* Path to TAR is invalid. */
return (-1);
}
if (pipe(pipe1) < 0) {
Error(cip, kDoPerror, "pipe to Tar failed");
return (-1);
}
pid1 = (int) fork();
if (pid1 < 0) {
(void) close(pipe1[0]);
(void) close(pipe1[1]);
return (-1);
} else if (pid1 == 0) {
/* Child */
if ((dstdir != NULL) && (dstdir[0] != '\0') && (chdir(dstdir) < 0)) {
Error(cip, kDoPerror, "tar chdir to %s failed", dstdir);
exit(1);
}
(void) close(pipe1[1]); /* close write end */
(void) dup2(pipe1[0], 0); /* use read end on stdin */
(void) close(pipe1[0]);
for (i=3; i<256; i++)
(void) close(i);
argv[0] = (char *) "tar";
argv[1] = (char *) "xpf";
argv[2] = (char *) "-";
argv[3] = NULL;
(void) execv(TAR, argv);
exit(1);
}
/* Parent */
*pid = pid1;
(void) close(pipe1[0]); /* close read end */
return (pipe1[1]); /* use write end */
} /* OpenTar */
static int
FTPGetOneTarF(const FTPCIPtr cip, const char *file, const char *const dstdir)
{
char *buf;
size_t bufSize;
int tmpResult;
volatile int result;
int nread, nwrote;
volatile int fd;
volatile int vfd;
const char *volatile vfile;
#ifndef NO_SIGNALS
int sj;
volatile FTPSigProc osigpipe;
volatile FTPCIPtr vcip;
#endif
int pid, status;
char savedCwd[512];
char *volatile basecp;
result = kNoErr;
cip->usingTAR = 0;
if ((file[0] == '\0') || ((file[0] == '/') && (file[1] == '\0'))) {
/* It was "/"
* We can't do that, because "get /.tar"
* or "get .tar" does not work.
*/
result = kErrOpenFailed;
cip->errNo = kErrOpenFailed;
return (result);
}
if (FTPCmd(cip, "MDTM %s.tar", file) == 2) {
/* Better not use this method since there is
* no way to tell if the server would use the
* existing .tar or do a new one on the fly.
*/
result = kErrOpenFailed;
cip->errNo = kErrOpenFailed;
return (result);
}
basecp = strrchr(file, '/');
if (basecp != NULL)
basecp = strrchr(file, '\\');
if (basecp != NULL) {
/* Need to cd to the parent directory and get it
* from there.
*/
if (FTPGetCWD(cip, savedCwd, sizeof(savedCwd)) != 0) {
result = kErrOpenFailed;
cip->errNo = kErrOpenFailed;
return (result);
}
result = FTPChdir(cip, file);
if (result != kNoErr) {
return (result);
}
result = FTPChdir(cip, "..");
if (result != kNoErr) {
(void) FTPChdir(cip, savedCwd);
return (result);
}
file = basecp + 1;
}
fd = OpenTar(cip, dstdir, &pid);
if (fd < 0) {
result = kErrOpenFailed;
cip->errNo = kErrOpenFailed;
if (basecp != NULL)
(void) FTPChdir(cip, savedCwd);
return (result);
}
vfd = fd;
vfile = file;
#ifndef NO_SIGNALS
vcip = cip;
osigpipe = (volatile FTPSigProc) signal(SIGPIPE, BrokenData);
gGotBrokenData = 0;
gCanBrokenDataJmp = 0;
#ifdef HAVE_SIGSETJMP
sj = sigsetjmp(gBrokenDataJmp, 1);
#else
sj = setjmp(gBrokenDataJmp);
#endif /* HAVE_SIGSETJMP */
if (sj != 0) {
(void) signal(SIGPIPE, (FTPSigProc) osigpipe);
FTPShutdownHost(vcip);
(void) signal(SIGPIPE, SIG_IGN);
(void) close(vfd);
for (;;) {
#ifdef HAVE_WAITPID
if ((waitpid(pid, &status, 0) < 0) && (errno != EINTR))
break;
#else
if ((wait(&status) < 0) && (errno != EINTR))
break;
#endif
if (WIFEXITED(status) || WIFSIGNALED(status))
break; /* done */
}
if (basecp != NULL)
(void) FTPChdir(cip, savedCwd);
vcip->errNo = kErrRemoteHostClosedConnection;
return(vcip->errNo);
}
gCanBrokenDataJmp = 1;
#endif /* NO_SIGNALS */
tmpResult = FTPStartDataCmd(cip, kNetReading, kTypeBinary, (longest_int) 0, "RETR %s.tar", vfile);
if (tmpResult < 0) {
result = tmpResult;
if (result == kErrGeneric)
result = kErrRETRFailed;
cip->errNo = result;
#ifndef NO_SIGNALS
(void) signal(SIGPIPE, SIG_IGN);
#endif
(void) close(vfd);
for (;;) {
#ifdef HAVE_WAITPID
if ((waitpid(pid, &status, 0) < 0) && (errno != EINTR))
break;
#else
if ((wait(&status) < 0) && (errno != EINTR))
break;
#endif
if (WIFEXITED(status) || WIFSIGNALED(status))
break; /* done */
}
#ifndef NO_SIGNALS
(void) signal(SIGPIPE, (FTPSigProc) osigpipe);
#endif
if (basecp != NULL)
(void) FTPChdir(cip, savedCwd);
return (result);
}
cip->usingTAR = 1;
buf = cip->buf;
bufSize = cip->bufSize;
FTPInitIOTimer(cip);
cip->lname = vfile; /* could be NULL */
cip->rname = vfile;
FTPStartIOTimer(cip);
/* Binary */
for (;;) {
if (! WaitForRemoteInput(cip)) { /* could set cancelXfer */
cip->errNo = result = kErrDataTimedOut;
Error(cip, kDontPerror, "Remote read timed out.\n");
break;
}
if (cip->cancelXfer > 0) {
FTPAbortDataTransfer(cip);
result = cip->errNo = kErrDataTransferAborted;
break;
}
#if !defined(NO_SIGNALS)
gCanBrokenDataJmp = 1;
if (cip->xferTimeout > 0)
(void) alarm(cip->xferTimeout);
#endif /* NO_SIGNALS */
#ifdef NO_SIGNALS
nread = SRead(cip->dataSocket, buf, bufSize, (int) cip->xferTimeout, kFullBufferNotRequired|kNoFirstSelect);
if (nread == kTimeoutErr) {
cip->errNo = result = kErrDataTimedOut;
Error(cip, kDontPerror, "Remote read timed out.\n");
break;
} else if (nread < 0) {
if (errno == EINTR)
continue;
Error(cip, kDoPerror, "Remote read failed.\n");
result = kErrSocketReadFailed;
cip->errNo = kErrSocketReadFailed;
break;
} else if (nread == 0) {
break;
}
#else
nread = read(cip->dataSocket, buf, bufSize);
if (nread < 0) {
if (errno == EINTR)
continue;
Error(cip, kDoPerror, "Remote read failed.\n");
result = kErrSocketReadFailed;
cip->errNo = kErrSocketReadFailed;
break;
} else if (nread == 0) {
break;
}
gCanBrokenDataJmp = 0;
#endif
nwrote = write(fd, buf, nread);
if (nwrote != nread) {
if ((gGotBrokenData != 0) || (errno == EPIPE)) {
result = kErrWriteFailed;
cip->errNo = kErrWriteFailed;
errno = EPIPE;
} else {
Error(cip, kDoPerror, "Local write failed.\n");
result = kErrWriteFailed;
cip->errNo = kErrWriteFailed;
}
break;
}
cip->bytesTransferred += (longest_int) nread;
FTPUpdateIOTimer(cip);
}
#if !defined(NO_SIGNALS)
if (cip->xferTimeout > 0)
(void) alarm(0);
gCanBrokenDataJmp = 0;
#endif /* NO_SIGNALS */
(void) close(fd);
for (;;) {
#ifdef HAVE_WAITPID
if ((waitpid(pid, &status, 0) < 0) && (errno != EINTR))
break;
#else
if ((wait(&status) < 0) && (errno != EINTR))
break;
#endif
if (WIFEXITED(status) || WIFSIGNALED(status))
break; /* done */
}
tmpResult = FTPEndDataCmd(cip, 1);
if ((tmpResult < 0) && (result == 0)) {
result = kErrRETRFailed;
cip->errNo = kErrRETRFailed;
}
FTPStopIOTimer(cip);
#if !defined(NO_SIGNALS)
(void) signal(SIGPIPE, (FTPSigProc) osigpipe);
#endif
if ((result == 0) && (cip->bytesTransferred == 0)) {
result = kErrOpenFailed;
cip->errNo = kErrOpenFailed;
}
if (basecp != NULL)
(void) FTPChdir(cip, savedCwd);
return (result);
} /* FTPGetOneTarF */
#endif /* TAR */
static int
FTPGetOneF(
const FTPCIPtr cip,
const char *const file,
const char *dstfile,
int xtype,
const int fdtouse,
longest_int expectedSize,
time_t mdtm,
const int resumeflag,
const int appendflag,
const int deleteflag,
const ConfirmResumeDownloadProc resumeProc)
{
char *buf;
size_t bufSize;
int tmpResult;
volatile int result;
int nread, nwrote;
volatile int fd;
#if ASCII_TRANSLATION
char *src, *srclim;
char *dst, *dstlim;
char outbuf[512];
#endif
volatile longest_int startPoint = 0;
struct utimbuf ut;
struct Stat st;
#if !defined(NO_SIGNALS)
volatile FTPSigProc osigpipe;
volatile FTPCIPtr vcip;
volatile int vfd, vfdtouse;
int sj;
#endif /* NO_SIGNALS */
volatile int created = 0;
int zaction = kConfirmResumeProcSaidBestGuess;
int statrc;
int noMdtmCheck;
time_t now;
if (cip->buf == NULL) {
Error(cip, kDoPerror, "Transfer buffer not allocated.\n");
cip->errNo = kErrNoBuf;
return (cip->errNo);
}
result = kNoErr;
cip->usingTAR = 0;
if (fdtouse < 0) {
/* Only ask for extended information
* if we have the name of the file
* and we didn't already have the
* info.
*
* Always ask for the modification time,
* because even if it was passed in it
* may not be accurate. This is often
* the case when it came from an ls
* listing, in which the local time
* zone could be a factor.
*
*/
AutomaticallyUseASCIIModeDependingOnExtension(cip, file, &xtype);
if (expectedSize == kSizeUnknown) {
(void) FTPFileSizeAndModificationTime(cip, file, &expectedSize, xtype, &mdtm);
} else {
(void) FTPFileModificationTime(cip, file, &mdtm);
}
/* For Get, we can't recover very well if it turns out restart
* didn't work, so check beforehand.
*/
if ((resumeflag == kResumeYes) || (resumeProc != NoConfirmResumeDownloadProc)) {
if (cip->hasREST == kCommandAvailabilityUnknown) {
(void) FTPSetTransferType(cip, kTypeBinary);
if (SetStartOffset(cip, (longest_int) 1) == kNoErr) {
/* Now revert -- we still may not end up
* doing it.
*/
SetStartOffset(cip, (longest_int) -1);
}
}
}
if (appendflag == kAppendYes) {
zaction = kConfirmResumeProcSaidAppend;
} else if (cip->hasREST == kCommandNotAvailable) {
zaction = kConfirmResumeProcSaidOverwrite;
} else if (resumeflag == kResumeYes) {
zaction = kConfirmResumeProcSaidBestGuess;
} else {
zaction = kConfirmResumeProcSaidOverwrite;
}
statrc = Stat(dstfile, &st);
if (statrc == 0) {
if (resumeProc != NULL) {
zaction = (*resumeProc)(
&dstfile,
(longest_int) st.st_size,
st.st_mtime,
file,
expectedSize,
mdtm,
&startPoint
);
}
if (zaction == kConfirmResumeProcSaidBestGuess) {
if (expectedSize != kSizeUnknown) {
/* We know the size of the remote file,
* and we have a local file too.
*
* Try and decide if we need to get
* the entire file, or just part of it.
*/
startPoint = (longest_int) st.st_size;
zaction = kConfirmResumeProcSaidResume;
/* If the local file exists and has a recent
* modification time (< 12 hours) and
* the remote file's modtime is not recent,
* then heuristically conclude that the
* local modtime should not be trusted
* (i.e. user killed the process before
* the local modtime could be preserved).
*/
noMdtmCheck = 0;
if (mdtm != kModTimeUnknown) {
time(&now);
if ((st.st_mtime > now) || (((now - st.st_mtime) < 46200) && ((now - mdtm) >= 46200)))
noMdtmCheck = 1;
}
if ((mdtm == kModTimeUnknown) || (noMdtmCheck != 0)) {
/* Can't use the timestamps as an aid. */
if (startPoint == expectedSize) {
/* Don't go to all the trouble of downloading nothing. */
cip->errNo = kErrLocalSameAsRemote;
if (deleteflag == kDeleteYes)
(void) FTPDelete(cip, file, kRecursiveNo, kGlobNo);
return (cip->errNo);
} else if (startPoint > expectedSize) {
/* Panic; odds are the file we have
* was a different file altogether,
* since it is larger than the
* remote copy. Re-do it all.
*/
zaction = kConfirmResumeProcSaidOverwrite;
} /* else resume at startPoint */
} else if ((mdtm == st.st_mtime) || (mdtm == (st.st_mtime - 1)) || (mdtm == (st.st_mtime + 1))) {
/* File has the same time.
* Note: Windows' file timestamps can be off by one second!
*/
if (startPoint == expectedSize) {
/* Don't go to all the trouble of downloading nothing. */
cip->errNo = kErrLocalSameAsRemote;
if (deleteflag == kDeleteYes)
(void) FTPDelete(cip, file, kRecursiveNo, kGlobNo);
return (cip->errNo);
} else if (startPoint > expectedSize) {
/* Panic; odds are the file we have
* was a different file altogether,
* since it is larger than the
* remote copy. Re-do it all.
*/
zaction = kConfirmResumeProcSaidOverwrite;
} else {
/* We have a file by the same time,
* but smaller start point. Leave
* the startpoint as is since it
* is most likely valid.
*/
}
} else if (mdtm < st.st_mtime) {
/* Remote file is older than
* local file. Don't overwrite
* our file.
*/
cip->errNo = kErrLocalFileNewer;
return (cip->errNo);
} else /* if (mdtm > st.st_mtime) */ {
/* File has a newer timestamp
* altogether, assume the remote
* file is an entirely new file
* and replace ours with it.
*/
zaction = kConfirmResumeProcSaidOverwrite;
}
} else {
zaction = kConfirmResumeProcSaidOverwrite;
}
}
} else {
zaction = kConfirmResumeProcSaidOverwrite;
}
if (zaction == kConfirmResumeProcSaidCancel) {
/* User wants to cancel this file and any
* remaining in batch.
*/
cip->errNo = kErrUserCanceled;
return (cip->errNo);
} else if (zaction == kConfirmResumeProcSaidSkip) {
/* Nothing done, but not an error. */
if (deleteflag == kDeleteYes)
(void) FTPDelete(cip, file, kRecursiveNo, kGlobNo);
return (kNoErr);
} else if (zaction == kConfirmResumeProcSaidResume) {
/* Resume; proc set the startPoint. */
if (startPoint == expectedSize) {
/* Don't go to all the trouble of downloading nothing. */
/* Nothing done, but not an error. */
if (deleteflag == kDeleteYes)
(void) FTPDelete(cip, file, kRecursiveNo, kGlobNo);
return (kNoErr);
} else if (startPoint > expectedSize) {
/* Cannot set start point past end of remote file */
cip->errNo = result = kErrSetStartPoint;
return (result);
}
fd = Open(dstfile, O_WRONLY|O_APPEND|O_BINARY, 00666);
} else if (zaction == kConfirmResumeProcSaidAppend) {
/* leave startPoint at zero, we will append everything. */
startPoint = (longest_int) 0;
fd = Open(dstfile, O_WRONLY|O_CREAT|O_APPEND|O_BINARY, 00666);
} else /* if (zaction == kConfirmResumeProcSaidOverwrite) */ {
created = 1;
startPoint = (longest_int) 0;
fd = Open(dstfile, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 00666);
}
if (fd < 0) {
Error(cip, kDoPerror, "Cannot open local file %s for writing.\n", dstfile);
result = kErrOpenFailed;
cip->errNo = kErrOpenFailed;
return (result);
}
if ((expectedSize == (longest_int) 0) && (startPoint <= (longest_int) 0) && (zaction != kConfirmResumeProcSaidOverwrite)) {
/* Don't go to all the trouble of downloading nothing. */
#if defined(WIN32) || defined(_WINDOWS)
/* Note: Windows doesn't allow zero-size files. */
(void) write(fd, "\r\n", 2);
#endif
(void) close(fd);
if (mdtm != kModTimeUnknown) {
cip->mdtm = mdtm;
(void) time(&ut.actime);
ut.modtime = mdtm;
(void) utime(dstfile, &ut);
}
if (deleteflag == kDeleteYes)
(void) FTPDelete(cip, file, kRecursiveNo, kGlobNo);
return (kNoErr);
}
} else {
fd = fdtouse;
}
if ((cip->numDownloads == 0) && (cip->dataSocketRBufSize > 0)) {
/* If dataSocketSBufSize is non-zero, it means you
* want to explicitly try to set the size of the
* socket's I/O buffer.
*
* If it is zero, it means you want to just use the
* TCP stack's default value, which is typically
* between 8 and 64 kB.
*
* If you try to set the buffer larger than 64 kB,
* the TCP stack should try to use RFC 1323 to
* negotiate "TCP Large Windows" which may yield
* significant performance gains.
*/
if (cip->hasRETRBUFSIZE == kCommandAvailable)
(void) FTPCmd(cip, "SITE RETRBUFSIZE %lu", (unsigned long) cip->dataSocketRBufSize);
else if (cip->hasRBUFSIZ == kCommandAvailable)
(void) FTPCmd(cip, "SITE RBUFSIZ %lu", (unsigned long) cip->dataSocketRBufSize);
else if (cip->hasRBUFSZ == kCommandAvailable)
(void) FTPCmd(cip, "SITE RBUFSZ %lu", (unsigned long) cip->dataSocketRBufSize);
else if (cip->hasBUFSIZE == kCommandAvailable)
(void) FTPCmd(cip, "SITE BUFSIZE %lu", (unsigned long) cip->dataSocketSBufSize);
}
#ifdef NO_SIGNALS
#else /* NO_SIGNALS */
vcip = cip;
vfdtouse = fdtouse;
vfd = fd;
osigpipe = (volatile FTPSigProc) signal(SIGPIPE, BrokenData);
gGotBrokenData = 0;
gCanBrokenDataJmp = 0;
#ifdef HAVE_SIGSETJMP
sj = sigsetjmp(gBrokenDataJmp, 1);
#else
sj = setjmp(gBrokenDataJmp);
#endif /* HAVE_SIGSETJMP */
if (sj != 0) {
(void) signal(SIGPIPE, (FTPSigProc) osigpipe);
if (vfdtouse < 0) {
(void) close(vfd);
}
FTPShutdownHost(vcip);
vcip->errNo = kErrRemoteHostClosedConnection;
return(vcip->errNo);
}
gCanBrokenDataJmp = 1;
#endif /* NO_SIGNALS */
tmpResult = FTPStartDataCmd(cip, kNetReading, xtype, startPoint, "RETR %s", file);
if (tmpResult < 0) {
result = tmpResult;
if (result == kErrGeneric)
result = kErrRETRFailed;
cip->errNo = result;
if (fdtouse < 0) {
(void) close(fd);
if ((created != 0) && (appendflag == kAppendNo) && (cip->startPoint == 0))
(void) unlink(dstfile);
}
#if !defined(NO_SIGNALS)
(void) signal(SIGPIPE, (FTPSigProc) osigpipe);
#endif /* NO_SIGNALS */
return (result);
}
if ((startPoint != 0) && (cip->startPoint == 0)) {
/* Remote could not or would not set the start offset
* to what we wanted.
*
* So now we have to undo our seek.
*/
if (Lseek(fd, (off_t) 0, SEEK_SET) != (off_t) 0) {
cip->errNo = kErrLseekFailed;
if (fdtouse < 0) {
(void) close(fd);
}
#if !defined(NO_SIGNALS)
(void) signal(SIGPIPE, (FTPSigProc) osigpipe);
#endif /* NO_SIGNALS */
return (cip->errNo);
}
startPoint = 0;
}
buf = cip->buf;
bufSize = cip->bufSize;
FTPInitIOTimer(cip);
cip->mdtm = mdtm;
(void) time(&ut.actime);
ut.modtime = mdtm;
cip->expectedSize = expectedSize;
cip->lname = dstfile; /* could be NULL */
cip->rname = file;
if (fdtouse >= 0)
cip->useProgressMeter = 0;
FTPStartIOTimer(cip);
#if ASCII_TRANSLATION
if (xtype == kTypeAscii) {
/* Ascii */
for (;;) {
if (! WaitForRemoteInput(cip)) { /* could set cancelXfer */
cip->errNo = result = kErrDataTimedOut;
Error(cip, kDontPerror, "Remote read timed out.\n");
break;
}
if (cip->cancelXfer > 0) {
FTPAbortDataTransfer(cip);
result = cip->errNo = kErrDataTransferAborted;
break;
}
#ifdef TESTING_ABOR
if (cip->bytesTransferred > 0) {
cip->cancelXfer = 1;
FTPAbortDataTransfer(cip);
result = cip->errNo = kErrDataTransferAborted;
break;
}
#endif /* TESTING_ABOR */
#ifdef NO_SIGNALS
nread = SRead(cip->dataSocket, buf, bufSize, (int) cip->xferTimeout, kFullBufferNotRequired|kNoFirstSelect);
if (nread == kTimeoutErr) {
cip->errNo = result = kErrDataTimedOut;
Error(cip, kDontPerror, "Remote read timed out.\n");
break;
} else if (nread < 0) {
if ((gGotBrokenData != 0) || (errno == EPIPE)) {
result = cip->errNo = kErrSocketReadFailed;
errno = EPIPE;
Error(cip, kDoPerror, "Lost data connection to remote host.\n");
} else if (errno == EINTR) {
continue;
} else {
Error(cip, kDoPerror, "Remote read failed.\n");
result = kErrSocketReadFailed;
cip->errNo = kErrSocketReadFailed;
}
break;
} else if (nread == 0) {
break;
}
#else
gCanBrokenDataJmp = 1;
if (cip->xferTimeout > 0)
(void) alarm(cip->xferTimeout);
nread = read(cip->dataSocket, buf, bufSize);
if (nread < 0) {
if ((gGotBrokenData != 0) || (errno == EPIPE)) {
result = cip->errNo = kErrSocketReadFailed;
errno = EPIPE;
Error(cip, kDoPerror, "Lost data connection to remote host.\n");
(void) shutdown(cip->dataSocket, 2);
} else if (errno == EINTR) {
continue;
} else {
result = cip->errNo = kErrSocketReadFailed;
Error(cip, kDoPerror, "Remote read failed.\n");
(void) shutdown(cip->dataSocket, 2);
}
break;
} else if (nread == 0) {
break;
}
gCanBrokenDataJmp = 0;
#endif /* NO_SIGNALS */
src = buf;
srclim = src + nread;
dst = outbuf;
dstlim = dst + sizeof(outbuf);
while (src < srclim) {
if (*src == '\r') {
src++;
continue;
}
if (dst >= dstlim) {
nwrote = write(fd, outbuf, (size_t) (dst - outbuf));
if (nwrote == (int) (dst - outbuf)) {
/* Success. */
dst = outbuf;
} else if ((gGotBrokenData != 0) || (errno == EPIPE)) {
result = kErrWriteFailed;
cip->errNo = kErrWriteFailed;
errno = EPIPE;
(void) shutdown(cip->dataSocket, 2);
goto brk;
} else {
Error(cip, kDoPerror, "Local write failed.\n");
result = kErrWriteFailed;
cip->errNo = kErrWriteFailed;
(void) shutdown(cip->dataSocket, 2);
goto brk;
}
}
*dst++ = *src++;
}
if (dst > outbuf) {
nwrote = write(fd, outbuf, (size_t) (dst - outbuf));
if (nwrote != (int) (dst - outbuf)) {
if ((gGotBrokenData != 0) || (errno == EPIPE)) {
result = kErrWriteFailed;
cip->errNo = kErrWriteFailed;
errno = EPIPE;
(void) shutdown(cip->dataSocket, 2);
goto brk;
} else {
Error(cip, kDoPerror, "Local write failed.\n");
result = kErrWriteFailed;
cip->errNo = kErrWriteFailed;
(void) shutdown(cip->dataSocket, 2);
goto brk;
}
}
}
if (mdtm != kModTimeUnknown) {
(void) utime(dstfile, &ut);
}
cip->bytesTransferred += (longest_int) nread;
FTPUpdateIOTimer(cip);
}
} else
#endif /* ASCII_TRANSLATION */
{
/* Binary */
for (;;) {
if (! WaitForRemoteInput(cip)) { /* could set cancelXfer */
cip->errNo = result = kErrDataTimedOut;
Error(cip, kDontPerror, "Remote read timed out.\n");
break;
}
if (cip->cancelXfer > 0) {
FTPAbortDataTransfer(cip);
result = cip->errNo = kErrDataTransferAborted;
break;
}
#ifdef TESTING_ABOR
if (cip->bytesTransferred > 0) {
cip->cancelXfer = 1;
FTPAbortDataTransfer(cip);
result = cip->errNo = kErrDataTransferAborted;
break;
}
#endif /* TESTING_ABOR */
#ifdef NO_SIGNALS
nread = SRead(cip->dataSocket, buf, bufSize, (int) cip->xferTimeout, kFullBufferNotRequired|kNoFirstSelect);
if (nread == kTimeoutErr) {
cip->errNo = result = kErrDataTimedOut;
Error(cip, kDontPerror, "Remote read timed out.\n");
break;
} else if (nread < 0) {
if ((gGotBrokenData != 0) || (errno == EPIPE)) {
result = cip->errNo = kErrSocketReadFailed;
errno = EPIPE;
Error(cip, kDoPerror, "Lost data connection to remote host.\n");
} else if (errno == EINTR) {
continue;
} else {
Error(cip, kDoPerror, "Remote read failed.\n");
result = kErrSocketReadFailed;
cip->errNo = kErrSocketReadFailed;
}
break;
} else if (nread == 0) {
break;
}
#else
gCanBrokenDataJmp = 1;
if (cip->xferTimeout > 0)
(void) alarm(cip->xferTimeout);
nread = read(cip->dataSocket, buf, bufSize);
if (nread < 0) {
if ((gGotBrokenData != 0) || (errno == EPIPE)) {
result = cip->errNo = kErrSocketReadFailed;
errno = EPIPE;
Error(cip, kDoPerror, "Lost data connection to remote host.\n");
} else if (errno == EINTR) {
continue;
} else {
result = cip->errNo = kErrSocketReadFailed;
Error(cip, kDoPerror, "Remote read failed.\n");
}
(void) shutdown(cip->dataSocket, 2);
break;
} else if (nread == 0) {
break;
}
gCanBrokenDataJmp = 0;
#endif /* NO_SIGNALS */
nwrote = write(fd, buf, nread);
if (nwrote != nread) {
if ((gGotBrokenData != 0) || (errno == EPIPE)) {
result = kErrWriteFailed;
cip->errNo = kErrWriteFailed;
errno = EPIPE;
} else {
Error(cip, kDoPerror, "Local write failed.\n");
result = kErrWriteFailed;
cip->errNo = kErrWriteFailed;
}
(void) shutdown(cip->dataSocket, 2);
break;
}
/* Ugggh... do this after each write operation
* so it minimizes the chance of a user killing
* the process before we reset the timestamps.
*/
if (mdtm != kModTimeUnknown) {
(void) utime(dstfile, &ut);
}
cip->bytesTransferred += (longest_int) nread;
FTPUpdateIOTimer(cip);
}
}
#if ASCII_TRANSLATION
brk:
#endif
#if !defined(NO_SIGNALS)
if (cip->xferTimeout > 0)
(void) alarm(0);
gCanBrokenDataJmp = 0;
#endif /* NO_SIGNALS */
if (fdtouse < 0) {
/* If they gave us a descriptor (fdtouse >= 0),
* leave it open, otherwise we opened it, so
* we need to close it.
*/
(void) close(fd);
fd = -1;
}
tmpResult = FTPEndDataCmd(cip, 1);
if ((tmpResult < 0) && (result == 0)) {
result = kErrRETRFailed;
cip->errNo = kErrRETRFailed;
}
FTPStopIOTimer(cip);
#if !defined(NO_SIGNALS)
(void) signal(SIGPIPE, (FTPSigProc) osigpipe);
#endif /* NO_SIGNALS */
if ((mdtm != kModTimeUnknown) && (cip->bytesTransferred > 0)) {
(void) utime(dstfile, &ut);
}
if (result == kNoErr) {
cip->numDownloads++;
if (deleteflag == kDeleteYes) {
result = FTPDelete(cip, file, kRecursiveNo, kGlobNo);
}
}
return (result);
} /* FTPGetOneF */
int
FTPGetOneFile3(
const FTPCIPtr cip,
const char *const file,
const char *const dstfile,
const int xtype,
const int fdtouse,
const int resumeflag,
const int appendflag,
const int deleteflag,
const ConfirmResumeDownloadProc resumeProc,
int UNUSED(reserved))
{
int result;
LIBNCFTP_USE_VAR(reserved);
if (cip == NULL)
return (kErrBadParameter);
if (strcmp(cip->magic, kLibraryMagic))
return (kErrBadMagic);
if ((file == NULL) || (file[0] == '\0'))
return (kErrBadParameter);
if (fdtouse < 0) {
if ((dstfile == NULL) || (dstfile[0] == '\0'))
return (kErrBadParameter);
}
result = FTPGetOneF(cip, file, dstfile, xtype, fdtouse, kSizeUnknown, kModTimeUnknown, resumeflag, appendflag, deleteflag, resumeProc);
return (result);
} /* FTPGetOneFile3 */
int
FTPGetFiles3(
const FTPCIPtr cip,
const char *pattern1,
const char *const dstdir1,
const int recurse,
int doGlob,
const int xtype,
const int resumeflag,
int appendflag,
const int deleteflag,
const int tarflag,
const ConfirmResumeDownloadProc resumeProc,
int UNUSED(reserved))
{
LineList globList;
LinePtr itemPtr;
FileInfoList files;
FileInfoPtr filePtr;
int batchResult;
int result;
char *ldir;
char *cp;
const char *dstdir;
const char *pattern;
char *pattern2, *dstdir2;
char c;
int recurse1;
int errRc;
LIBNCFTP_USE_VAR(reserved);
if (cip == NULL)
return (kErrBadParameter);
if (strcmp(cip->magic, kLibraryMagic))
return (kErrBadMagic);
if (pattern1 == NULL)
return (kErrBadParameter);
dstdir2 = NULL;
pattern2 = NULL;
if (dstdir1 == NULL) {
dstdir = NULL;
} else {
dstdir2 = StrDup(dstdir1);
if (dstdir2 == NULL) {
errRc = kErrMallocFailed;
goto return_err;
}
StrRemoveTrailingLocalPathDelim(dstdir2);
dstdir = dstdir2;
}
pattern2 = StrDup(pattern1);
if (pattern2 == NULL) {
errRc = kErrMallocFailed;
goto return_err;
}
StrRemoveTrailingSlashes(pattern2);
pattern = pattern2;
if (pattern[0] == '\0') {
if (recurse == kRecursiveNo) {
errRc = kErrBadParameter;
goto return_err;
}
pattern = ".";
doGlob = kGlobNo;
} else if (strcmp(pattern, ".") == 0) {
if (recurse == kRecursiveNo) {
errRc = kErrBadParameter;
goto return_err;
}
doGlob = kGlobNo;
}
if (recurse == kRecursiveYes)
appendflag = kAppendNo;
batchResult = FTPRemoteGlob(cip, &globList, pattern, doGlob);
if (batchResult != kNoErr) {
errRc = batchResult;
goto return_err;
}
cip->cancelXfer = 0; /* should already be zero */
for (itemPtr = globList.first; itemPtr != NULL; itemPtr = itemPtr->next) {
if ((recurse == kRecursiveYes) && (FTPIsDir(cip, itemPtr->line) > 0)) {
#ifdef TAR
if ((tarflag == kTarYes) && (xtype == kTypeBinary) && (appendflag == kAppendNo) && (deleteflag == kDeleteNo) && (FTPGetOneTarF(cip, itemPtr->line, dstdir) == kNoErr)) {
/* Great! */
continue;
}
#endif /* TAR */
(void) FTPRemoteRecursiveFileList1(cip, itemPtr->line, &files);
(void) ComputeLNames(&files, itemPtr->line, dstdir, 1);
recurse1 = recurse;
} else {
recurse1 = kRecursiveNo;
(void) LineToFileInfoList(itemPtr, &files);
(void) ComputeRNames(&files, ".", 0, 1);
(void) ComputeLNames(&files, NULL, dstdir, 0);
}
if (cip->cancelXfer > 0) {
DisposeFileInfoListContents(&files);
break;
}
#if 0
for (filePtr = files.first; filePtr != NULL; filePtr = filePtr->next) {
PrintF(cip, " R=%s, L=%s, 2=%s, size=%d, mdtm=%u, type=%c\n",
filePtr->rname,
filePtr->lname,
filePtr->rlinkto ? filePtr->rlinkto : "",
filePtr->size,
(unsigned int) filePtr->mdtm,
filePtr->type
);
}
#endif
for (filePtr = files.first; filePtr != NULL; filePtr = filePtr->next) {
if (cip->connected == 0) {
if (batchResult == kNoErr)
batchResult = kErrRemoteHostClosedConnection;
break;
}
if (filePtr->type == 'd') {
#if defined(WIN32) || defined(_WINDOWS)
(void) MkDirs(filePtr->lname, 00777);
#else
(void) mkdir(filePtr->lname, 00777);
#endif
} else if (filePtr->type == 'l') {
/* skip it -- we do that next pass. */
} else if (recurse1 != kRecursiveYes) {
result = FTPGetOneF(cip, filePtr->rname, filePtr->lname, xtype, -1, filePtr->size, filePtr->mdtm, resumeflag, appendflag, deleteflag, resumeProc);
if (files.nFileInfos == 1) {
if (result != kNoErr)
batchResult = result;
} else {
if ((result != kNoErr) && (result != kErrLocalFileNewer) && (result != kErrRemoteFileNewer) && (result != kErrLocalSameAsRemote))
batchResult = result;
}
if (result == kErrUserCanceled)
cip->cancelXfer = 1;
if (cip->cancelXfer > 0)
break;
} else {
ldir = filePtr->lname;
cp = StrRFindLocalPathDelim(ldir);
if (cp != NULL) {
while (cp > ldir) {
if (! IsLocalPathDelim(*cp)) {
++cp;
break;
}
--cp;
}
if (cp > ldir) {
c = *cp;
*cp = '\0';
if (MkDirs(ldir, 00777) < 0) {
Error(cip, kDoPerror, "Could not create local directory \"%s\"\n", ldir);
batchResult = kErrGeneric;
*cp = c;
continue;
}
*cp = c;
}
}
result = FTPGetOneF(cip, filePtr->rname, filePtr->lname, xtype, -1, filePtr->size, filePtr->mdtm, resumeflag, appendflag, deleteflag, resumeProc);
if (files.nFileInfos == 1) {
if (result != kNoErr)
batchResult = result;
} else {
if ((result != kNoErr) && (result != kErrLocalFileNewer) && (result != kErrRemoteFileNewer) && (result != kErrLocalSameAsRemote))
batchResult = result;
}
if (result == kErrUserCanceled)
cip->cancelXfer = 1;
if (cip->cancelXfer > 0)
break;
}
}
if (cip->cancelXfer > 0) {
DisposeFileInfoListContents(&files);
break;
}
#ifdef HAVE_SYMLINK
for (filePtr = files.first; filePtr != NULL; filePtr = filePtr->next) {
if (filePtr->type == 'l') {
(void) unlink(filePtr->lname);
if (symlink(filePtr->rlinkto, filePtr->lname) < 0) {
Error(cip, kDoPerror, "Could not symlink %s to %s\n", filePtr->rlinkto, filePtr->lname);
/* Note: not worth setting batchResult */
}
}
}
#endif /* HAVE_SYMLINK */
DisposeFileInfoListContents(&files);
}
DisposeLineListContents(&globList);
if (batchResult < 0)
cip->errNo = batchResult;
errRc = batchResult;
return_err:
if (dstdir2 != NULL)
free(dstdir2);
if (pattern2 != NULL)
free(pattern2);
return (errRc);
} /* FTPGetFiles3 */
/*------------------------- wrappers for old routines ----------------------*/
int
FTPGetOneFile(const FTPCIPtr cip, const char *const file, const char *const dstfile)
{
return (FTPGetOneFile3(cip, file, dstfile, kTypeBinary, -1, kResumeNo, kAppendNo, kDeleteNo, (ConfirmResumeDownloadProc) 0, 0));
} /* FTPGetOneFile */
int
FTPGetOneFile2(const FTPCIPtr cip, const char *const file, const char *const dstfile, const int xtype, const int fdtouse, const int resumeflag, const int appendflag)
{
return (FTPGetOneFile3(cip, file, dstfile, xtype, fdtouse, resumeflag, appendflag, kDeleteNo, (ConfirmResumeDownloadProc) 0, 0));
} /* FTPGetOneFile2 */
int
FTPGetFiles(const FTPCIPtr cip, const char *const pattern, const char *const dstdir, const int recurse, const int doGlob)
{
return (FTPGetFiles3(cip, pattern, dstdir, recurse, doGlob, kTypeBinary, kResumeNo, kAppendNo, kDeleteNo, kTarYes, (ConfirmResumeDownloadProc) 0, 0));
} /* FTPGetFiles */
int
FTPGetFiles2(const FTPCIPtr cip, const char *const pattern, const char *const dstdir, const int recurse, const int doGlob, const int xtype, const int resumeflag, const int appendflag)
{
return (FTPGetFiles3(cip, pattern, dstdir, recurse, doGlob, xtype, resumeflag, appendflag, kDeleteNo, kTarYes, (ConfirmResumeDownloadProc) 0, 0));
} /* FTPGetFiles2 */
int
FTPGetOneFileAscii(const FTPCIPtr cip, const char *const file, const char *const dstfile)
{
return (FTPGetOneFile3(cip, file, dstfile, kTypeAscii, -1, kResumeNo, kAppendNo, kDeleteNo, (ConfirmResumeDownloadProc) 0, 0));
} /* FTPGetOneFileAscii */
int
FTPGetFilesAscii(const FTPCIPtr cip, const char *const pattern, const char *const dstdir, const int recurse, const int doGlob)
{
return (FTPGetFiles3(cip, pattern, dstdir, recurse, doGlob, kTypeAscii, kResumeNo, kAppendNo, kDeleteNo, kTarNo, (ConfirmResumeDownloadProc) 0, 0));
} /* FTPGetFilesAscii */
int
FTPPutOneFile(const FTPCIPtr cip, const char *const file, const char *const dstfile)
{
return (FTPPutOneFile3(cip, file, dstfile, kTypeBinary, -1, 0, NULL, NULL, kResumeNo, kDeleteNo, NoConfirmResumeUploadProc, 0));
} /* FTPPutOneFile */
int
FTPPutOneFile2(const FTPCIPtr cip, const char *const file, const char *const dstfile, const int xtype, const int fdtouse, const int appendflag, const char *const tmppfx, const char *const tmpsfx)
{
return (FTPPutOneFile3(cip, file, dstfile, xtype, fdtouse, appendflag, tmppfx, tmpsfx, kResumeNo, kDeleteNo, NoConfirmResumeUploadProc, 0));
} /* FTPPutOneFile2 */
int
FTPPutFiles(const FTPCIPtr cip, const char *const pattern, const char *const dstdir, const int recurse, const int doGlob)
{
return (FTPPutFiles3(cip, pattern, dstdir, recurse, doGlob, kTypeBinary, 0, NULL, NULL, kResumeNo, kDeleteNo, NoConfirmResumeUploadProc, 0));
} /* FTPPutFiles */
int
FTPPutFiles2(const FTPCIPtr cip, const char *const pattern, const char *const dstdir, const int recurse, const int doGlob, const int xtype, const int appendflag, const char *const tmppfx, const char *const tmpsfx)
{
return (FTPPutFiles3(cip, pattern, dstdir, recurse, doGlob, xtype, appendflag, tmppfx, tmpsfx, kResumeNo, kDeleteNo, NoConfirmResumeUploadProc, 0));
} /* FTPPutFiles2 */
int
FTPPutOneFileAscii(const FTPCIPtr cip, const char *const file, const char *const dstfile)
{
return (FTPPutOneFile3(cip, file, dstfile, kTypeAscii, -1, 0, NULL, NULL, kResumeNo, kDeleteNo, NoConfirmResumeUploadProc, 0));
} /* FTPPutOneFileAscii */
int
FTPPutFilesAscii(const FTPCIPtr cip, const char *const pattern, const char *const dstdir, const int recurse, const int doGlob)
{
return (FTPPutFiles3(cip, pattern, dstdir, recurse, doGlob, kTypeAscii, 0, NULL, NULL, kResumeNo, kDeleteNo, NoConfirmResumeUploadProc, 0));
} /* FTPPutFilesAscii */
int
FTPListToMemory(const FTPCIPtr cip, const char *const pattern, const LineListPtr llines, const char *const lsflags)
{
return (FTPListToMemory2(cip, pattern, llines, lsflags, 1, (int *) 0));
} /* FTPListToMemory */
/* eof IO.c */