--- /dev/null
+/* 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 */