mirror of
https://github.com/reactos/reactos.git
synced 2024-08-24 20:28:57 +00:00
2825 lines
71 KiB
C
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 */
|