/* Get.c */ #include "Sys.h" #include #ifdef HAVE_UTIME_H # include #else struct utimbuf {time_t actime; time_t modtime;}; #endif #include "Util.h" #include "RCmd.h" #include "Xfer.h" #include "Cmds.h" #include "Glob.h" #include "Get.h" #include "DateSize.h" #include "List.h" #include "Getopt.h" int gMayUTime = kDoUTime; /* User variable. */ extern longstring gPager; extern longstring gRemoteCWD; extern longstring gLocalCWD; extern int gTransferType, gXferAbortFlag; extern int gStdout, gWinInit; extern size_t gXferBufSize; extern char *gOptArg, *gXferBuf; extern int gOptInd; int BinaryGet(XferSpecPtr xp) { int result; /* This is supposed to be done previously, if you wanted accurate * file sizes from GetDateAndSize. We don't do a SETBINARY here. * Instead, we set it to gTransferType, which we know is not * ascii. Most often this will mean binary mode, but perhaps we're * dealing with a tenex machine. */ SetType(gTransferType); xp->xProc = StdFileReceive; /* xp->inStream = gDataSocket; RDataCmd fills this in when it gets it. */ /* Send the request and do the transfer. */ result = RDataCmd(xp, "RETR %s", xp->remoteFileName); return (result); } /* BinaryGet */ int AsciiGet(XferSpecPtr xp) { int result; /* This is supposed to be done previously, if you wanted accurate * file sizes from GetDateAndSize. */ SETASCII; /* Setup the parameter block to give to RDataCmd. */ xp->xProc = StdAsciiFileReceive; /* xp->inStream = gDataSocket; RDataCmd fills this in when it gets it. */ /* Send the request and do the transfer. */ result = RDataCmd(xp, "RETR %s", xp->remoteFileName); return (result); } /* AsciiGet */ /* From the pathname given in remoteName, get a local filename for * the current local directory. Then open the actual file for writing. */ static void GetLocalName(GetOptionsPtr gopt, string localName) { char *cp; if ((cp = gopt->lName) == NULL) { /* We're supposed to pick it. */ cp = strrchr(gopt->rName, '/'); if (cp == NULL) cp = gopt->rName; else cp++; } gopt->lName = Strncpy(localName, cp, sizeof(string)); } /* GetLocalName */ void SetLocalFileTimes(int doUTime, time_t remoteModTime, char *lname) { struct utimbuf ut; /* Restore the modifcation date of the new file to * what it was on the remote host, if possible. */ if ((doUTime == kDoUTime) && (remoteModTime != kModTimeUnknown)) { time(&ut.actime); ut.modtime = remoteModTime; (void) utime(lname, &ut); } } /* SetLocalFileTimes */ int TruncReOpenReceiveFile(XferSpecPtr xp) { int fd; close(xp->outStream); fd = open(xp->localFileName, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (fd < 0) { /* Should never get here, since we were able to open this * very file for appending earlier. */ Error(kDoPerror, "Can't re-open local file %s.\n", xp->localFileName); /* Try to give it something to write to anyway. */ fd = open("/dev/null", O_WRONLY); xp->outStream = fd; if (fd < 0) { /* Don't core, please. */ Exit(kExitPanic); } return (-1); } xp->outStream = fd; return (0); } /* TruncReOpenReceiveFile */ int DoGet(GetOptionsPtr gopt) { int fd; int result; string local; long fileSize; time_t modifTime; int doReports; struct stat st; size_t restartPt; const char *mode = "w"; time_t now; XferSpecPtr xp; if (gTransferType == 'A') { /* Have to set the type here, because GetDateAndSize() may * use the SIZE command, and the result of that depends * on the current transfer type setting. */ SETASCII; } else { SetType(gTransferType); } /* See if we can get some info about the file first. */ fileSize = GetDateAndSize(gopt->rName, &modifTime); restartPt = SZ(0); doReports = 0; if (gopt->outputMode == kDumpToStdout) { fd = gStdout; STRNCPY(local, kLocalFileIsStdout); /* Don't have progress reports going if we're piping or * dumping to the screen. */ } else { GetLocalName(gopt, local); if (stat(local, &st) == 0) { /* File exists on the local host. We must decide whether * we really want to fetch this file, since we might have * it here already. But when in doubt, we will go ahead * and fetch the file. */ if (gopt->forceReget) { /* If the local file is smaller, then we * should attempt to restart the transfer * from where we left off. */ if ((st.st_size < fileSize) || (fileSize == kSizeUnknown)) { restartPt = SZ(st.st_size); mode = "a"; DebugMsg("Manually continuing local file %s.", local); } else { PrintF("Already have %s with size %lu.\n", gopt->rName, fileSize); return (0); } } else if (!gopt->overwrite) { if (modifTime != kModTimeUnknown) { /* We know the date of the remote file. */ DebugMsg("Local file %s has size %lu and is dated %s", local, (unsigned long) st.st_size, ctime(&st.st_mtime) ); if (modifTime < st.st_mtime) { /* Remote file is older than existing local file. */ PrintF("Already have %s.\n", gopt->rName); return (0); } else if (modifTime == st.st_mtime) { /* Remote file is same age. */ if (fileSize != kSizeUnknown) { /* If the local file is smaller, then we * should attempt to restart the transfer * from where we left off, since we the remote * file has the same date. */ if (st.st_size < fileSize) { restartPt = SZ(st.st_size); mode = "a"; } else if (st.st_size == fileSize) { PrintF("Already have %s.\n", gopt->rName); return (0); } else { DebugMsg("Overwriting %s; local file has same date,\n", gopt->lName); DebugMsg("but local file is larger, so fetching remote version anyway.\n"); } } else { DebugMsg("Overwriting %s; local file has same date,\n", gopt->lName); DebugMsg("but can't determine remote size, so fetching remote version anyway.\n"); } } else { /* Remote file is more recent. Fetch the * whole file. */ DebugMsg("Overwriting %s; remote was newer.\n", gopt->lName); } } else { /* We don't know the date of the file. * We won't be able to safely assume anything about * the remote file. It is legal to have a more * recent remote file (which we don't know), with a * smaller (or greater, or equal even) size. We * will just have to fetch it no matter what. */ DebugMsg("Overwriting %s; couldn't determine remote file date.\n", gopt->lName); } } else { DebugMsg("Explicitly overwriting %s.\n", gopt->lName); } } else { /* We don't have a local file with the same name as the remote, * but we may also want to avoid doing the transfer of this * file. For example, this is where we check the remote * file's date if we were told to only get files which are * less than X days old. */ if (gopt->newer > 0) { time(&now); if (((unsigned long) now - (unsigned long) (gopt->newer * 86400)) > (unsigned long) modifTime) { DebugMsg("Skipping %s, older than %d days.\n", gopt->rName, gopt->newer); return (0); } } } if (*mode == 'w') fd = open(local, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); else fd = open(local, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (fd < 0) { Error(kDoPerror, "Can't open local file %s.\n", local); return (-1); } doReports = gopt->doReports; } xp = InitXferSpec(); xp->netMode = kNetReading; xp->outStream = fd; /* This group is needed for the progress reporting and logging stuff. * Otherwise, it isn't that important. */ xp->doReports = doReports; xp->localFileName = local; xp->remoteFileName = gopt->rName; xp->expectedSize = fileSize; xp->startPoint = restartPt; xp->doUTime = gopt->doUTime; xp->remoteModTime = modifTime; if (gTransferType == 'A') { result = AsciiGet(xp); } else { result = BinaryGet(xp); } if (fd != gStdout) { (void) close(fd); if ((result < 0) && (xp->bytesTransferred < 1L) && (*mode != 'a')) { /* An error occurred, and we didn't transfer anything, * so remove empty file we just made. */ (void) UNLINK(local); } else { /* Restore the modifcation date of the new file to * what it was on the remote host, if possible. */ SetLocalFileTimes(gopt->doUTime, modifTime, local); } } DoneWithXferSpec(xp); return (result); } /* DoGet */ void InitGetOutputMode(GetOptionsPtr gopt, int outputMode) { gopt->outputMode = outputMode; if (outputMode == kSaveToDisk) { gopt->doUTime = gMayUTime; gopt->doReports = 1; } else { gopt->doUTime = kDontUTime; gopt->doReports = 0; } } /* InitGetOutputMode */ void InitGetOptions(GetOptionsPtr gopt) { PTRZERO(gopt, sizeof(GetOptions)); } /* InitGetOptions */ int SetGetOption(GetOptionsPtr gopt, int opt, char *optArg) { int i; switch (opt) { case 'C': /* Force continuation */ gopt->forceReget = 1; break; case 'f': /* Force overwrite (no reget, no newer) */ gopt->overwrite = 1; break; case 'G': /* No glob */ gopt->noGlob = 1; break; case 'R': /* Recursive */ gopt->recursive = 1; break; case 'n': /* Get files if not X days or newer. */ i = atoi(optArg); if (i <= 0) { EPrintF("Option to -n must be greater than zero.\n"); return (kUsageErr); } gopt->newer = i; break; case 'z': /* Get one file x, and save as y. */ gopt->saveAs = 1; break; default: return (kUsageErr); } return (kNoErr); } /* SetGetOption */ int GetGetOptions(int argc, char **argv, GetOptionsPtr gopt) { int opt; /* When this is called, we are always writing to disk. * In other words, we have no colon-mode to worry about. */ InitGetOptions(gopt); InitGetOutputMode(gopt, kSaveToDisk); /* Tell Getopt() that we want to start over with a new command. */ GetoptReset(); while ((opt = Getopt(argc, argv, "CfGRn:z")) >= 0) { if (SetGetOption(gopt, opt, gOptArg) == kUsageErr) return (kUsageErr); } return (kNoErr); } /* GetGetOptions */ #ifdef HAVE_SYMLINK static int GetSymLinkInfo(char *dst, size_t siz, char *rLink) { LineList fileList; char *cp; int result; result = -1; *dst = '\0'; InitLineList(&fileList); ListToMemory(&fileList, "LIST", kListDirNamesOnlyMode, rLink); if (fileList.first != NULL) { cp = fileList.first->line; *cp++ = '\0'; for (cp += strlen(cp) - 1; ; cp--) { if (*cp == '\0') goto done; if ((cp[0] == '>') && (cp[-1] == '-')) break; } (void) Strncpy(dst, cp + 2, siz); result = 0; } done: DisposeLineListContents(&fileList); return (result); } /* GetSymLinkInfo */ #endif /* HAVE_SYMLINK */ int GetDir(GetOptionsPtr gopt, char *dName, char *rRoot, char *lRoot) { LineList dirFiles; LinePtr dirFile; char *rd; /* Remote directory path. */ char *ld; /* Local directory path. */ char *rf; /* Complete remote pathname for an item. */ char *lf; /* Complete local pathname for an item. */ char *sl; /* What a symlink points to. */ char *iName; int fType; rd = NULL; ld = NULL; rf = NULL; lf = NULL; if ((rd = StrDup(rRoot)) == NULL) goto fail; if ((rd = PtrCatSlash(rd, dName)) == NULL) goto fail; if ((ld = StrDup(lRoot)) == NULL) goto fail; if ((ld = PtrCatSlash(ld, dName)) == NULL) goto fail; /* Create this directory on the local host first. */ if (MkDirs(ld)) { EPrintF("Could not create directory '%s.'\n", ld); goto fail; } /* Get the names of all files and subdirs. */ InitLineList(&dirFiles); GetFileList(&dirFiles, rd); /* Get all the files first. */ for (dirFile = dirFiles.first; dirFile != NULL; dirFile = dirFile->next) { fType = (int) dirFile->line[0]; if ((fType == '-') || (fType == 'l')) { iName = dirFile->line + 1; if ((rf = StrDup(rd)) == NULL) goto fail; if ((rf = PtrCatSlash(rf, iName)) == NULL) goto fail; if ((lf = StrDup(ld)) == NULL) goto fail; if ((lf = PtrCatSlash(lf, iName)) == NULL) goto fail; if (fType == '-') { gopt->rName = rf; gopt->lName = lf; DoGet(gopt); } else { #ifdef HAVE_SYMLINK sl = (char *) malloc(SZ(512)); if (sl != NULL) { if (GetSymLinkInfo(sl, SZ(512), rf) == 0) (void) symlink(sl, lf); free(sl); } #endif /* HAVE_SYMLINK */ } free(rf); free(lf); rf = NULL; lf = NULL; } if (gXferAbortFlag == SIGINT) break; /* Don't get rest of files if you interrupted. */ } /* Now get subdirectories. */ for (dirFile = dirFiles.first; dirFile != NULL; dirFile = dirFile->next) { if (gXferAbortFlag == SIGINT) break; /* Don't get rest of files if you interrupted. */ fType = (int) dirFile->line[0]; if (fType == 'd') { iName = dirFile->line + 1; if (GetDir(gopt, iName, rd, ld) < 0) break; } } free(ld); free(rd); DisposeLineListContents(&dirFiles); return (0); fail: if (rd != NULL) free(rd); if (ld != NULL) free(ld); if (rf != NULL) free(rf); if (lf != NULL) free(lf); return (-1); } /* GetDir */ int RemoteFileType(char *fName) { LineList fileList; char *cp; int result; int i; result = 0; InitLineList(&fileList); ListToMemory(&fileList, "LIST", kListDirNamesOnlyMode, fName); if (fileList.first != NULL) { cp = fileList.first->line; /* Do a quick check and see if it looks like a unix ls line. */ for (i=1; i<=3; i++) if ((cp[i] != 'r') && (cp[i] != 'w') && (cp[i] != 'x') && (cp[i] != '-')) goto done; result = (int) cp[0]; } done: DisposeLineListContents(&fileList); return (result); } /* RemoteFileType */ int DoGetWithGlobbingAndRecursion(GetOptionsPtr gopt) { int err; LineList globFiles; LinePtr globFile; char *cp; int fType; int result; longstring rcwd; err = 0; InitLineList(&globFiles); RemoteGlob(&globFiles, gopt->rName, kListNoFlags); for (globFile = globFiles.first; globFile != NULL; globFile = globFile->next) { if (gXferAbortFlag == SIGINT) break; /* Don't get rest of files if you interrupted. */ if (gopt->recursive) { fType = RemoteFileType(globFile->line); if (fType == 'd') { if ((cp = strrchr(globFile->line, '/')) != NULL) { /* If the user said something like * "get -R /pub/a/b/c/d" we want to just write the * contents of the 'd' as a subdirectory of the local * directory, and not create ./pub, ./pub/a, etc. */ STRNCPY(rcwd, gRemoteCWD); *cp++ = '\0'; if (DoChdir(globFile->line) == 0) { GetDir(gopt, cp, gRemoteCWD, gLocalCWD); } /* Restore the directory we were in before. */ (void) DoChdir(rcwd); } else { /* Otherwise, the user gave a simple path, so it was * something like "get -R pub" */ GetDir(gopt, globFile->line, gRemoteCWD, gLocalCWD); } } else if (fType == 'l') { EPrintF("Ignoring symbolic link '%s'\n", globFile->line); } else if (fType == '-') { goto regFile; } } else { regFile: gopt->rName = globFile->line; gopt->lName = NULL; /* Make it later. */ result = DoGet(gopt); if (result < 0) err = -1; } } DisposeLineListContents(&globFiles); return (err); } /* DoGetWithGlobbingAndRecursion */ /* Fetch one or more remote files. */ int GetCmd(int argc, char **argv) { int i, result, errs; GetOptions gopt; if (GetGetOptions(argc, argv, &gopt) == kUsageErr) return (kUsageErr); argv += gOptInd; argc -= gOptInd; errs = 0; if (gopt.noGlob || gopt.saveAs) { for (i=0; ioutStream; for (;;) { nread = BufferGets(xbuf, sizeof(xbuf), xp); if (nread <= 0) break; MakeStringPrintable(buf2, (unsigned char *) xbuf, sizeof(buf2)); MultiLinePrintF("%s", buf2); } return (nread); /* 0 or -1 */ } /* CatFileToScreenProc */ /* Dump a remote file to the screen. */ int DoCat(char *remoteName) { int result; XferSpecPtr xp; MultiLineInit(); xp = InitXferSpec(); xp->netMode = kNetReading; xp->outStream = gStdout; /* This group is needed for the progress reporting and logging stuff. * Otherwise, it isn't that important. */ xp->doReports = kNoReports; xp->localFileName = kLocalFileIsStdout; xp->remoteFileName = remoteName; /* This is supposed to be done previously, if you wanted accurate * file sizes from GetDateAndSize. */ SETASCII; /* Setup the parameter block to give to RDataCmd. */ if (!isatty(xp->outStream)) xp->xProc = StdAsciiFileReceive; /* Faster */ else xp->xProc = CatFileToScreenProc; /* xp->inStream = gDataSocket; RDataCmd fills this in when it gets it. */ /* Send the request and do the transfer. */ result = RDataCmd(xp, "RETR %s", xp->remoteFileName); DoneWithXferSpec(xp); return (result); } /* DoCat */ /* We need to make something we can give to popen. This is simple * if it is a plain file, but if they wanted to page a compressed * file we have to prepend the correct filter before the pager name. */ int MakePageCmdLine(char *cmd, size_t siz, char *remote_file) { int useZCat; int useGZCat; int binaryPage; int len; useZCat = 0; useGZCat = 0; binaryPage = 0; len = (int) strlen(remote_file); if (len > 2) { if (remote_file[len - 2] == '.') { /* Check for .Z files. */ if (remote_file[len-1] == 'Z') useZCat = 1; /* Check for .z (gzip) files. */ if (remote_file[len - 1] == 'z') useGZCat = 1; } } if (len > 3) { /* Check for ".gz" (gzip) files. */ if (STREQ(remote_file + len - 3, ".gz")) useGZCat = 1; } /* Run compressed remote files through zcat, then the pager. * If GZCAT was defined, we also try paging gzipped files. */ if (useGZCat) { #ifdef GZCAT (void) Strncpy(cmd, GZCAT, siz); (void) Strncat(cmd, " | ", siz); (void) Strncat(cmd, gPager, siz); #else PrintF("NcFTP wasn't configured to page gzipped files.\n"); #endif } else if (useZCat) { #ifdef ZCAT (void) Strncpy(cmd, ZCAT, siz); (void) Strncat(cmd, " | ", siz); (void) Strncat(cmd, gPager, siz); #else # ifdef GZCAT /* gzcat can do .Z's also. */ (void) Strncpy(cmd, GZCAT, siz); (void) Strncat(cmd, " | ", siz); (void) Strncat(cmd, gPager, siz); # else PrintF("NcFTP wasn't configured to page compressed files.\n"); # endif #endif } else { (void) Strncpy(cmd, gPager, siz); } binaryPage = (useZCat || useGZCat); return (binaryPage); } /* MakePageCmdLine */ /* View a remote file through your pager. */ int DoPage(char *remoteName) { FILE *fp; int result; longstring pageCmd; int binaryPage; XferSpecPtr xp; binaryPage = MakePageCmdLine(pageCmd, sizeof(pageCmd), remoteName); DebugMsg("%s page: %s\n", binaryPage ? "Binary" : "Ascii", pageCmd ); fp = POpen(pageCmd, "w", 1); if (fp == NULL) { Error(kDoPerror, "Could not run %s.\n", pageCmd); return -1; } xp = InitXferSpec(); xp->netMode = kNetReading; xp->outStream = fileno(fp); /* This group is needed for the progress reporting and logging stuff. * Otherwise, it isn't that important. */ xp->doReports = kNoReports; xp->localFileName = kLocalFileIsStdout; xp->remoteFileName = remoteName; if (!binaryPage) { /* Try to use text mode for paging, so newlines get converted. */ result = AsciiGet(xp); } else { /* Must use binary, or else zcat will complain about corrupted * input files, since we'd be converting carriage-returns. */ result = BinaryGet(xp); } DoneWithXferSpec(xp); (void) PClose(fp); RestoreScreen(1); return (result); } /* DoPage */ /* View one or more remote files through your pager. */ int PageCmd(int argc, char **argv) { int i, result, errs; LineList globFiles; LinePtr globFile; char *pagerProg; if (STREQ(argv[1], "-b") && (gWinInit > 0) && (argc > 2)) { /* A hack to let you use the built-in pager like you * can with the lpage command. */ pagerProg = NULL; /* Use built-in */ i = 2; } else { if (gPager[0] == '\0') { EPrintF("You haven't specified a program to use as a pager.\n"); EPrintF("You can set this from the preferences screen (prefs command).\n"); return -1; } pagerProg = gPager; i = 1; } for (errs=0; inext) { if (pagerProg == NULL) result = DoCat(globFile->line); else result = DoPage(globFile->line); if (result < 0) --errs; if (gXferAbortFlag == SIGINT) break; /* Don't get rest of files if you interrupted. */ } DisposeLineListContents(&globFiles); } return (errs); } /* PageCmd */ /* View one or more remote files through your pager. */ int CatCmd(int argc, char **argv) { int i, result, errs; LineList globFiles; LinePtr globFile; MultiLineInit(); for (i=1, errs=0; inext) { result = DoCat(globFile->line); if (result < 0) --errs; if (gXferAbortFlag == SIGINT) break; /* Don't get rest of files if you interrupted. */ if (argc > 2) MultiLinePrintF("### End of file %s ###\n", globFile->line); } DisposeLineListContents(&globFiles); } return (errs); } /* CatCmd */