home *** CD-ROM | disk | FTP | other *** search
/ Big Green CD 8 / BGCD_8_Dev.iso / NEXTSTEP / UNIX / Networking / ncftp-2.4.2-MIHS / src / Get.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-02-21  |  21.6 KB  |  950 lines

  1. /* Get.c */
  2.  
  3. #include "Sys.h"
  4.  
  5. #include <signal.h>
  6.  
  7. #ifdef HAVE_UTIME_H
  8. #    include <utime.h>
  9. #else
  10. struct    utimbuf {time_t actime; time_t modtime;};
  11. #endif 
  12.  
  13. #include "Util.h"
  14. #include "RCmd.h"
  15. #include "Xfer.h"
  16. #include "Cmds.h"
  17. #include "Glob.h"
  18. #include "Get.h"
  19. #include "DateSize.h"
  20. #include "List.h"
  21. #include "Getopt.h"
  22.  
  23. int gMayUTime = kDoUTime;    /* User variable. */
  24.  
  25. extern longstring gPager;
  26. extern longstring gRemoteCWD;
  27. extern longstring gLocalCWD;
  28. extern int gTransferType, gXferAbortFlag;
  29. extern int gStdout, gWinInit;
  30. extern size_t gXferBufSize;
  31. extern char *gOptArg, *gXferBuf;
  32. extern int gOptInd;
  33.  
  34. int BinaryGet(XferSpecPtr xp)
  35. {
  36.     int result;
  37.  
  38.     /* This is supposed to be done previously, if you wanted accurate
  39.      * file sizes from GetDateAndSize.  We don't do a SETBINARY here.
  40.      * Instead, we set it to gTransferType, which we know is not
  41.      * ascii.  Most often this will mean binary mode, but perhaps we're
  42.      * dealing with a tenex machine.
  43.      */
  44.     SetType(gTransferType);
  45.  
  46.     xp->xProc = StdFileReceive;
  47.     /* xp->inStream = gDataSocket;  RDataCmd fills this in when it gets it. */
  48.  
  49.     /* Send the request and do the transfer. */
  50.     result = RDataCmd(xp, "RETR %s", xp->remoteFileName);
  51.  
  52.     return (result);
  53. }    /* BinaryGet */
  54.  
  55.  
  56.  
  57.  
  58.  
  59. int AsciiGet(XferSpecPtr xp)
  60. {
  61.     int result;
  62.  
  63.     /* This is supposed to be done previously, if you wanted accurate
  64.      * file sizes from GetDateAndSize.
  65.      */
  66.     SETASCII;
  67.     
  68.     /* Setup the parameter block to give to RDataCmd. */
  69.     xp->xProc = StdAsciiFileReceive;
  70.     /* xp->inStream = gDataSocket;  RDataCmd fills this in when it gets it. */
  71.  
  72.     /* Send the request and do the transfer. */
  73.     result = RDataCmd(xp, "RETR %s", xp->remoteFileName);
  74.  
  75.     return (result);
  76. }    /* AsciiGet */
  77.  
  78.  
  79.  
  80. /* From the pathname given in remoteName, get a local filename for
  81.  * the current local directory.  Then open the actual file for writing.
  82.  */
  83. static
  84. void GetLocalName(GetOptionsPtr gopt, string localName)
  85. {
  86.     char *cp;
  87.  
  88.     if ((cp = gopt->lName) == NULL) {
  89.         /* We're supposed to pick it. */
  90.         cp = strrchr(gopt->rName, '/');
  91.         if (cp == NULL)
  92.             cp = gopt->rName;
  93.         else
  94.             cp++;
  95.     }
  96.     gopt->lName = Strncpy(localName, cp, sizeof(string));
  97. }    /* GetLocalName */
  98.  
  99.  
  100.  
  101.  
  102. void SetLocalFileTimes(int doUTime, time_t remoteModTime, char *lname)
  103. {
  104.     struct utimbuf ut;
  105.  
  106.     /* Restore the modifcation date of the new file to
  107.      * what it was on the remote host, if possible.
  108.      */
  109.     if ((doUTime == kDoUTime) && (remoteModTime != kModTimeUnknown)) {
  110.         time(&ut.actime);
  111.         ut.modtime = remoteModTime;
  112.         (void) utime(lname, &ut);
  113.     }
  114. }    /* SetLocalFileTimes */
  115.  
  116.  
  117.  
  118.  
  119. int TruncReOpenReceiveFile(XferSpecPtr xp)
  120. {
  121.     int fd;
  122.     
  123.     close(xp->outStream);
  124.     fd = open(xp->localFileName, O_WRONLY | O_TRUNC | O_CREAT,
  125.                 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
  126.     if (fd < 0) {
  127.         /* Should never get here, since we were able to open this
  128.          * very file for appending earlier.
  129.          */
  130.         Error(kDoPerror, "Can't re-open local file %s.\n", xp->localFileName);
  131.         
  132.         /* Try to give it something to write to anyway. */
  133.         fd = open("/dev/null", O_WRONLY);
  134.         xp->outStream = fd;
  135.         
  136.         if (fd < 0) {
  137.             /* Don't core, please. */
  138.             Exit(kExitPanic);
  139.         }
  140.         return (-1);
  141.     }
  142.     xp->outStream = fd;
  143.     return (0);
  144. }    /* TruncReOpenReceiveFile */
  145.  
  146.  
  147.  
  148.  
  149. int DoGet(GetOptionsPtr gopt)
  150. {
  151.     int fd;
  152.     int result;
  153.     string local;
  154.     long fileSize;
  155.     time_t modifTime;
  156.     int doReports;
  157.     struct stat st;
  158.     size_t restartPt;
  159.     char *mode;
  160.     time_t now;
  161.     XferSpecPtr xp;
  162.  
  163.     if (gTransferType == 'A') {
  164.         /* Have to set the type here, because GetDateAndSize() may
  165.          * use the SIZE command, and the result of that depends
  166.          * on the current transfer type setting.
  167.          */
  168.         SETASCII;
  169.     } else {
  170.         SetType(gTransferType);
  171.     }
  172.     
  173.     /* See if we can get some info about the file first. */
  174.     fileSize = GetDateAndSize(gopt->rName, &modifTime);
  175.     restartPt = SZ(0);
  176.     doReports = 0;
  177.  
  178.     if (gopt->outputMode == kDumpToStdout) {
  179.         fd = gStdout;
  180.         STRNCPY(local, kLocalFileIsStdout);
  181.         /* Don't have progress reports going if we're piping or
  182.          * dumping to the screen.
  183.          */
  184.     } else {
  185.         GetLocalName(gopt, local);
  186.         mode = "w";
  187.         if (stat(local, &st) == 0) {
  188.             /* File exists on the local host.  We must decide whether
  189.              * we really want to fetch this file, since we might have
  190.              * it here already.  But when in doubt, we will go ahead
  191.              * and fetch the file.
  192.              */
  193.             if (gopt->forceReget) {
  194.                 /* If the local file is smaller, then we
  195.                  * should attempt to restart the transfer
  196.                  * from where we left off.
  197.                  */
  198.                 if ((st.st_size < fileSize) || (fileSize == kSizeUnknown)) {
  199.                     restartPt = SZ(st.st_size);
  200.                     mode = "a";
  201.                     DebugMsg("Manually continuing local file %s.", local);
  202.                 } else {
  203.                     PrintF("Already have %s with size %lu.\n", gopt->rName, fileSize);
  204.                     return (0);
  205.                 }
  206.             } else if (!gopt->overwrite) {
  207.                 if (modifTime != kModTimeUnknown) {
  208.                     /* We know the date of the remote file. */
  209.                     DebugMsg("Local file %s has size %lu and is dated %s",
  210.                         local,
  211.                         (unsigned long) st.st_size,
  212.                         ctime(&st.st_mtime)
  213.                     );
  214.                     if (modifTime < st.st_mtime) {
  215.                         /* Remote file is older than existing local file. */
  216.                         PrintF("Already have %s.\n", gopt->rName);
  217.                         return (0);
  218.                     } else if (modifTime == st.st_mtime) {
  219.                         /* Remote file is same age. */
  220.                         if (fileSize != kSizeUnknown) {
  221.                             /* If the local file is smaller, then we
  222.                              * should attempt to restart the transfer
  223.                              * from where we left off, since we the remote
  224.                              * file has the same date.
  225.                              */
  226.                             if (st.st_size < fileSize) {
  227.                                 restartPt = SZ(st.st_size);
  228.                                 mode = "a";
  229.                             } else if (st.st_size == fileSize) {
  230.                                 PrintF("Already have %s.\n", gopt->rName);
  231.                                 return (0);
  232.                             } else {
  233.                                 DebugMsg("Overwriting %s; local file has same date,\n",
  234.                                     gopt->lName);
  235.                                 DebugMsg("but local file is larger, so fetching remote version anyway.\n");
  236.                             }
  237.                         } else {
  238.                             DebugMsg("Overwriting %s; local file has same date,\n",
  239.                                     gopt->lName);
  240.                             DebugMsg("but can't determine remote size, so fetching remote version anyway.\n");
  241.                         }
  242.                     } else {
  243.                         /* Remote file is more recent.  Fetch the
  244.                          * whole file.
  245.                          */
  246.                         DebugMsg("Overwriting %s; remote was newer.\n",
  247.                             gopt->lName);
  248.                     }
  249.                 } else {
  250.                     /* We don't know the date of the file.
  251.                      * We won't be able to safely assume anything about
  252.                      * the remote file.  It is legal to have a more
  253.                      * recent remote file (which we don't know), with a
  254.                      * smaller (or greater, or equal even) size.  We
  255.                      * will just have to fetch it no matter what.
  256.                      */
  257.                     DebugMsg("Overwriting %s; couldn't determine remote file date.\n",
  258.                         gopt->lName);
  259.                 }
  260.             } else {
  261.                 DebugMsg("Explicitly overwriting %s.\n", gopt->lName);
  262.             }
  263.         } else {
  264.             /* We don't have a local file with the same name as the remote,
  265.              * but we may also want to avoid doing the transfer of this
  266.              * file.  For example, this is where we check the remote
  267.              * file's date if we were told to only get files which are
  268.              * less than X days old.
  269.              */
  270.             if (gopt->newer > 0) {
  271.                 time(&now);
  272.                 if (((unsigned long) now - (unsigned long) (gopt->newer * 86400)) > (unsigned long) modifTime) {
  273.                     DebugMsg("Skipping %s, older than %d days.\n",
  274.                         gopt->rName, gopt->newer);
  275.                     return (0);
  276.                 }
  277.             }
  278.         }
  279.         if (*mode == 'w')
  280.             fd = open(local, O_WRONLY | O_TRUNC | O_CREAT,
  281.                 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
  282.         else
  283.             fd = open(local, O_WRONLY | O_APPEND | O_CREAT,
  284.                 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
  285.         if (fd < 0) {
  286.             Error(kDoPerror, "Can't open local file %s.\n", local);
  287.             return (-1);
  288.         }
  289.         doReports = gopt->doReports;
  290.     }
  291.  
  292.     xp = InitXferSpec();
  293.     xp->netMode = kNetReading;
  294.     xp->outStream = fd;
  295.     
  296.     /* This group is needed for the progress reporting and logging stuff.
  297.      * Otherwise, it isn't that important.
  298.      */
  299.     xp->doReports = doReports;
  300.     xp->localFileName = local;
  301.     xp->remoteFileName = gopt->rName;
  302.     xp->expectedSize = fileSize;
  303.     xp->startPoint = restartPt;
  304.     xp->doUTime = gopt->doUTime;
  305.     xp->remoteModTime = modifTime;
  306.     
  307.     if (gTransferType == 'A') {
  308.         result = AsciiGet(xp);
  309.     } else {        
  310.         result = BinaryGet(xp);
  311.     }
  312.  
  313.     if (fd != gStdout) {
  314.         (void) close(fd);
  315.         if ((result < 0) && (xp->bytesTransferred < 1L)) {
  316.             /* An error occurred, and we didn't transfer anything,
  317.              * so remove empty file we just made.
  318.              */
  319.             (void) UNLINK(local);
  320.         } else {
  321.             /* Restore the modifcation date of the new file to
  322.              * what it was on the remote host, if possible.
  323.              */
  324.             SetLocalFileTimes(gopt->doUTime, modifTime, local);
  325.         }
  326.     }
  327.  
  328.     DoneWithXferSpec(xp);
  329.     return (result);
  330. }    /* DoGet */
  331.  
  332.  
  333.  
  334.  
  335. void InitGetOutputMode(GetOptionsPtr gopt, int outputMode)
  336. {
  337.     gopt->outputMode = outputMode;
  338.     if (outputMode == kSaveToDisk) {
  339.         gopt->doUTime = gMayUTime;
  340.         gopt->doReports = 1;
  341.     } else {
  342.         gopt->doUTime = kDontUTime;    
  343.         gopt->doReports = 0;
  344.     }
  345. }    /* InitGetOutputMode */
  346.  
  347.  
  348.  
  349.  
  350. void InitGetOptions(GetOptionsPtr gopt)
  351. {
  352.     PTRZERO(gopt, sizeof(GetOptions));
  353. }    /* InitGetOptions */
  354.  
  355.  
  356.  
  357.  
  358. int SetGetOption(GetOptionsPtr gopt, int opt, char *optArg)
  359. {
  360.     int i;
  361.  
  362.     switch (opt) {        
  363.         case 'C':    /* Force continuation */
  364.             gopt->forceReget = 1;
  365.             break;
  366.         case 'f':    /* Force overwrite (no reget, no newer) */
  367.             gopt->overwrite = 1;
  368.             break;
  369.         case 'G':    /* No glob */
  370.             gopt->noGlob = 1;
  371.             break;
  372.         case 'R':    /* Recursive */
  373.             gopt->recursive = 1;
  374.             break;
  375.         case 'n':    /* Get files if not X days or newer.  */
  376.             i = atoi(optArg);
  377.             if (i <= 0) {
  378.                 EPrintF("Option to -n must be greater than zero.\n");
  379.                 return (kUsageErr);
  380.             }
  381.             gopt->newer = i;
  382.             break;
  383.         case 'z':    /* Get one file x, and save as y. */
  384.             gopt->saveAs = 1;
  385.             break;
  386.         default:
  387.             return (kUsageErr);
  388.     }
  389.     return (kNoErr);
  390. }    /* SetGetOption */
  391.  
  392.  
  393.  
  394.  
  395. int GetGetOptions(int argc, char **argv, GetOptionsPtr gopt)
  396. {
  397.     int opt;
  398.  
  399.     /* When this is called, we are always writing to disk.
  400.      * In other words, we have no colon-mode to worry about.
  401.      */
  402.     InitGetOptions(gopt);
  403.     InitGetOutputMode(gopt, kSaveToDisk);
  404.     
  405.     /* Tell Getopt() that we want to start over with a new command. */
  406.     GetoptReset();
  407.     while ((opt = Getopt(argc, argv, "CfGRn:z")) >= 0) {
  408.         if (SetGetOption(gopt, opt, gOptArg) == kUsageErr)
  409.             return (kUsageErr);
  410.     }
  411.     return (kNoErr);
  412. }    /* GetGetOptions */
  413.  
  414.  
  415.  
  416.  
  417. #ifdef HAVE_SYMLINK
  418. static
  419. int GetSymLinkInfo(char *dst, size_t siz, char *rLink)
  420. {
  421.     LineList fileList;
  422.     char *cp;
  423.     int result;
  424.  
  425.     result = -1;
  426.     *dst = '\0';
  427.     InitLineList(&fileList);
  428.     ListToMemory(&fileList, "LIST", kListDirNamesOnlyMode, rLink);
  429.     if (fileList.first != NULL) {
  430.         cp = fileList.first->line;
  431.         *cp++ = '\0';
  432.         for (cp += strlen(cp) - 1; ; cp--) {
  433.             if (*cp == '\0')
  434.                 goto done;
  435.             if ((cp[0] == '>') && (cp[-1] == '-'))
  436.                 break;
  437.         }
  438.         (void) Strncpy(dst, cp + 2, siz);
  439.         result = 0;
  440.     }
  441. done:
  442.     DisposeLineListContents(&fileList);
  443.     return (result);
  444. }    /* GetSymLinkInfo */
  445. #endif    /* HAVE_SYMLINK */
  446.  
  447.  
  448.  
  449. int GetDir(GetOptionsPtr gopt, char *dName, char *rRoot, char *lRoot)
  450. {
  451.     LineList dirFiles;
  452.     LinePtr dirFile;
  453.     char *rd;    /* Remote directory path. */
  454.     char *ld;    /* Local directory path. */
  455.     char *rf;    /* Complete remote pathname for an item. */
  456.     char *lf;    /* Complete local pathname for an item. */ 
  457.     char *sl;    /* What a symlink points to. */
  458.     char *iName;
  459.     int fType;
  460.  
  461.     rd = NULL;
  462.     ld = NULL;
  463.     rf = NULL;
  464.     lf = NULL;
  465.     
  466.     if ((rd = StrDup(rRoot)) == NULL)
  467.         goto fail;
  468.     if ((rd = PtrCatSlash(rd, dName)) == NULL)
  469.         goto fail;
  470.  
  471.     if ((ld = StrDup(lRoot)) == NULL)
  472.         goto fail;
  473.     if ((ld = PtrCatSlash(ld, dName)) == NULL)
  474.         goto fail;
  475.     
  476.     /* Create this directory on the local host first. */
  477.     if (MkDirs(ld)) {
  478.         EPrintF("Could not create directory '%s.'\n", ld);
  479.         goto fail;
  480.     }
  481.     
  482.     /* Get the names of all files and subdirs. */
  483.     InitLineList(&dirFiles);
  484.     GetFileList(&dirFiles, rd);
  485.  
  486.     /* Get all the files first. */
  487.     for (dirFile = dirFiles.first; dirFile != NULL; dirFile = dirFile->next) {
  488.         fType = (int) dirFile->line[0];
  489.         if ((fType == '-') || (fType == 'l')) {
  490.             iName = dirFile->line + 1;
  491.             if ((rf = StrDup(rd)) == NULL)
  492.                 goto fail;
  493.             if ((rf = PtrCatSlash(rf, iName)) == NULL)
  494.                 goto fail;
  495.             if ((lf = StrDup(ld)) == NULL)
  496.                 goto fail;
  497.             if ((lf = PtrCatSlash(lf, iName)) == NULL)
  498.                 goto fail;
  499.             if (fType == '-') {
  500.                 gopt->rName = rf;
  501.                 gopt->lName = lf;
  502.                 DoGet(gopt);
  503.             } else {
  504. #ifdef HAVE_SYMLINK
  505.                 sl = (char *) malloc(SZ(512));
  506.                 if (sl != NULL) {
  507.                     if (GetSymLinkInfo(sl, SZ(512), rf) == 0)
  508.                         (void) symlink(sl, lf);
  509.                     free(sl);
  510.                 }
  511. #endif    /* HAVE_SYMLINK */
  512.             }
  513.             free(rf);
  514.             free(lf);
  515.             rf = NULL;
  516.             lf = NULL;
  517.         }
  518.         if (gXferAbortFlag == SIGINT)
  519.             break;    /* Don't get rest of files if you interrupted. */
  520.     }
  521.     
  522.     /* Now get subdirectories. */
  523.     for (dirFile = dirFiles.first; dirFile != NULL; dirFile = dirFile->next) {
  524.         if (gXferAbortFlag == SIGINT)
  525.             break;    /* Don't get rest of files if you interrupted. */
  526.         fType = (int) dirFile->line[0];
  527.         if (fType == 'd') {
  528.             iName = dirFile->line + 1;
  529.             if (GetDir(gopt, iName, rd, ld) < 0)
  530.                 break;
  531.         }
  532.     }
  533.  
  534.     free(ld);
  535.     free(rd);
  536.     DisposeLineListContents(&dirFiles);
  537.     return (0);
  538.     
  539. fail:
  540.     if (rd != NULL)
  541.         free(rd);
  542.     if (ld != NULL)
  543.         free(ld);
  544.     if (rf != NULL)
  545.         free(rf);
  546.     if (lf != NULL)
  547.         free(lf);
  548.     return (-1);
  549. }    /* GetDir */
  550.  
  551.  
  552.  
  553.  
  554. int RemoteFileType(char *fName)
  555. {
  556.     LineList fileList;
  557.     char *cp;
  558.     int result;
  559.     int i;
  560.  
  561.     result = 0;
  562.     InitLineList(&fileList);
  563.     ListToMemory(&fileList, "LIST", kListDirNamesOnlyMode, fName);
  564.     if (fileList.first != NULL) {
  565.         cp = fileList.first->line;
  566.         /* Do a quick check and see if it looks like a unix ls line. */
  567.         for (i=1; i<=3; i++)
  568.             if ((cp[i] != 'r') && (cp[i] != 'w') && (cp[i] != 'x') && (cp[i] != '-'))
  569.                 goto done;
  570.         result = (int) cp[0];
  571.     }
  572. done:
  573.     DisposeLineListContents(&fileList);
  574.     return (result);
  575. }    /* RemoteFileType */
  576.  
  577.  
  578.  
  579.  
  580.  
  581. int DoGetWithGlobbingAndRecursion(GetOptionsPtr gopt)
  582. {
  583.     int err;
  584.     LineList globFiles;
  585.     LinePtr globFile;
  586.     char *cp;
  587.     int fType;
  588.     int result;
  589.     longstring rcwd;
  590.         
  591.     err = 0;
  592.     InitLineList(&globFiles);
  593.     RemoteGlob(&globFiles, gopt->rName, kListNoFlags);
  594.     
  595.     for (globFile = globFiles.first; globFile != NULL;
  596.         globFile = globFile->next)
  597.     {
  598.         if (gXferAbortFlag == SIGINT)
  599.             break;    /* Don't get rest of files if you interrupted. */
  600.         if (gopt->recursive) {
  601.             fType = RemoteFileType(globFile->line);
  602.             if (fType == 'd') {
  603.                 if ((cp = strrchr(globFile->line, '/')) != NULL) {
  604.                     /* If the user said something like
  605.                      * "get -R /pub/a/b/c/d" we want to just write the
  606.                      * contents of the 'd' as a subdirectory of the local
  607.                      * directory, and not create ./pub, ./pub/a, etc.
  608.                      */
  609.                     STRNCPY(rcwd, gRemoteCWD);
  610.                     *cp++ = '\0';
  611.                     if (DoChdir(globFile->line) == 0) {
  612.                         GetDir(gopt, cp, gRemoteCWD, gLocalCWD);
  613.                     }
  614.                     /* Restore the directory we were in before. */
  615.                     (void) DoChdir(rcwd);
  616.                 } else {
  617.                     /* Otherwise, the user gave a simple path, so it was
  618.                      * something like "get -R pub"
  619.                      */
  620.                     GetDir(gopt, globFile->line, gRemoteCWD, gLocalCWD);
  621.                 }
  622.             } else if (fType == 'l') {
  623.                 EPrintF("Ignoring symbolic link '%s'\n",
  624.                     globFile->line);
  625.             } else if (fType == '-') {
  626.                 goto regFile;
  627.             }
  628.         } else {
  629. regFile:
  630.             gopt->rName = globFile->line;
  631.             gopt->lName = NULL;    /* Make it later. */
  632.             result = DoGet(gopt);
  633.             if (result < 0)
  634.                 err = -1;
  635.         }
  636.     }
  637.     DisposeLineListContents(&globFiles);
  638.     
  639.     return (err);
  640. }    /* DoGetWithGlobbingAndRecursion */
  641.  
  642.  
  643.  
  644.  
  645. /* Fetch one or more remote files. */
  646. int GetCmd(int argc, char **argv)
  647. {
  648.     int i, result, errs;
  649.     GetOptions gopt;
  650.     
  651.     
  652.     if (GetGetOptions(argc, argv, &gopt) == kUsageErr)
  653.         return (kUsageErr);
  654.         
  655.     argv += gOptInd;
  656.     argc -= gOptInd;
  657.     errs = 0;
  658.  
  659.     if (gopt.noGlob || gopt.saveAs) {
  660.         for (i=0; i<argc; i++) {
  661.             gopt.rName = argv[i];
  662.             if (gopt.saveAs) {
  663.                 if (++i < argc)
  664.                     gopt.lName = argv[i];    /* Use this name. */
  665.                 else
  666.                     return (kUsageErr);
  667.             } else {
  668.                 gopt.lName = NULL;    /* Make it later. */
  669.             }
  670.             result = DoGet(&gopt);
  671.             if (gXferAbortFlag == SIGINT)
  672.                 break;    /* Don't get rest of files if you interrupted. */
  673.             if (result < 0)
  674.                 --errs;
  675.         }
  676.     } else {
  677.         for (i=0; i<argc; i++) {
  678.             gopt.rName = argv[i];
  679.             errs += DoGetWithGlobbingAndRecursion(&gopt);
  680.             if (gXferAbortFlag == SIGINT)
  681.                 break;    /* Don't get rest of files if you interrupted. */
  682.         }
  683.     }
  684.     
  685.     return (errs);
  686. }    /* GetCmd */
  687.  
  688.  
  689.  
  690. int
  691. CatFileToScreenProc(XferSpecPtr xp)
  692. {
  693.     int nread;
  694.     int fd;
  695.     char xbuf[256];
  696.     char buf2[256];
  697.  
  698.     fd = xp->outStream;
  699.     for (;;) {
  700.         nread = BufferGets(xbuf, sizeof(xbuf), xp);
  701.         if (nread <= 0)
  702.             break;
  703.  
  704.         MakeStringPrintable(buf2, (unsigned char *) xbuf, sizeof(buf2));
  705.         MultiLinePrintF("%s", buf2);
  706.     }
  707.     return (nread);    /* 0 or -1 */
  708. }    /* CatFileToScreenProc */
  709.  
  710.  
  711.  
  712.  
  713. /* Dump a remote file to the screen. */
  714. int DoCat(char *remoteName)
  715. {
  716.     int result;
  717.     XferSpecPtr xp;
  718.  
  719.     MultiLineInit();
  720.  
  721.     xp = InitXferSpec();
  722.     xp->netMode = kNetReading;
  723.     xp->outStream = gStdout;
  724.     
  725.     /* This group is needed for the progress reporting and logging stuff.
  726.      * Otherwise, it isn't that important.
  727.      */
  728.     xp->doReports = kNoReports;
  729.     xp->localFileName = kLocalFileIsStdout;
  730.     xp->remoteFileName = remoteName;
  731.  
  732.     /* This is supposed to be done previously, if you wanted accurate
  733.      * file sizes from GetDateAndSize.
  734.      */
  735.     SETASCII;
  736.     
  737.     /* Setup the parameter block to give to RDataCmd. */
  738.     if (isatty(xp->outStream))
  739.         xp->xProc = StdAsciiFileReceive;    /* Faster */
  740.     else
  741.         xp->xProc = CatFileToScreenProc;
  742.     /* xp->inStream = gDataSocket;  RDataCmd fills this in when it gets it. */
  743.  
  744.     /* Send the request and do the transfer. */
  745.     result = RDataCmd(xp, "RETR %s", xp->remoteFileName);
  746.     DoneWithXferSpec(xp);
  747.     return (result);
  748. }    /* DoCat */
  749.  
  750.  
  751.  
  752.  
  753. /* We need to make something we can give to popen.  This is simple
  754.  * if it is a plain file, but if they wanted to page a compressed
  755.  * file we have to prepend the correct filter before the pager name.
  756.  */
  757. int MakePageCmdLine(char *cmd, size_t siz, char *remote_file)
  758. {
  759.     int useZCat;
  760.     int useGZCat;
  761.     int binaryPage;
  762.     int len;
  763.     
  764.     useZCat = 0;
  765.     useGZCat = 0;
  766.     binaryPage = 0;
  767.     
  768.     len = (int) strlen(remote_file);
  769.  
  770.     if (len > 2) {
  771.              if (remote_file[len - 2] == '.') {
  772.             /* Check for .Z files. */
  773.             if (remote_file[len-1] == 'Z')
  774.                 useZCat = 1;
  775.  
  776.             /* Check for .z (gzip) files. */
  777.             if (remote_file[len - 1] == 'z')
  778.                 useGZCat = 1;
  779.         }
  780.     }
  781.  
  782.     if (len > 3) {
  783.         /* Check for ".gz" (gzip) files. */
  784.         if (STREQ(remote_file + len - 3, ".gz"))
  785.             useGZCat = 1;
  786.     }
  787.  
  788.     /* Run compressed remote files through zcat, then the pager.
  789.      * If GZCAT was defined, we also try paging gzipped files.
  790.      */    
  791.     if (useGZCat) {
  792. #ifdef GZCAT
  793.         (void) Strncpy(cmd, GZCAT, siz);
  794.         (void) Strncat(cmd, " | ", siz);
  795.         (void) Strncat(cmd, gPager, siz);
  796. #else
  797.         PrintF("NcFTP wasn't configured to page gzipped files.\n");
  798. #endif
  799.     } else if (useZCat) {
  800. #ifdef ZCAT
  801.         (void) Strncpy(cmd, ZCAT, siz);
  802.         (void) Strncat(cmd, " | ", siz);
  803.         (void) Strncat(cmd, gPager, siz);
  804. #else
  805. #    ifdef GZCAT
  806.         /* gzcat can do .Z's also. */
  807.         (void) Strncpy(cmd, GZCAT, siz);
  808.         (void) Strncat(cmd, " | ", siz);
  809.         (void) Strncat(cmd, gPager, siz);
  810. #    else
  811.         PrintF("NcFTP wasn't configured to page compressed files.\n");
  812. #    endif
  813. #endif
  814.     } else {
  815.         (void) Strncpy(cmd, gPager, siz);
  816.     }
  817.  
  818.     binaryPage = (useZCat || useGZCat);
  819.     return (binaryPage);
  820. }    /* MakePageCmdLine */
  821.  
  822.  
  823.  
  824.  
  825. /* View a remote file through your pager. */
  826. int DoPage(char *remoteName)
  827. {
  828.     FILE *fp;
  829.     int result;
  830.     longstring pageCmd;
  831.     int binaryPage;
  832.     XferSpecPtr xp;
  833.  
  834.     binaryPage = MakePageCmdLine(pageCmd, sizeof(pageCmd), remoteName);
  835.     DebugMsg("%s page: %s\n",
  836.         binaryPage ? "Binary" : "Ascii",
  837.         pageCmd
  838.     );
  839.  
  840.     fp = POpen(pageCmd, "w", 1);
  841.     if (fp == NULL) {
  842.         Error(kDoPerror, "Could not run %s.\n", pageCmd);
  843.         return -1;
  844.     }
  845.  
  846.     xp = InitXferSpec();
  847.     xp->netMode = kNetReading;
  848.     xp->outStream = fileno(fp);
  849.     
  850.     /* This group is needed for the progress reporting and logging stuff.
  851.      * Otherwise, it isn't that important.
  852.      */
  853.     xp->doReports = kNoReports;
  854.     xp->localFileName = kLocalFileIsStdout;
  855.     xp->remoteFileName = remoteName;
  856.  
  857.     if (!binaryPage) {
  858.         /* Try to use text mode for paging, so newlines get converted. */
  859.         result = AsciiGet(xp);
  860.     } else {
  861.         /* Must use binary, or else zcat will complain about corrupted
  862.          * input files, since we'd be converting carriage-returns.
  863.          */
  864.         result = BinaryGet(xp);
  865.     }
  866.     DoneWithXferSpec(xp);
  867.     (void) PClose(fp);
  868.     RestoreScreen(1);
  869.     return (result);
  870. }    /* DoPage */
  871.  
  872.  
  873.  
  874.  
  875. /* View one or more remote files through your pager. */
  876. int PageCmd(int argc, char **argv)
  877. {
  878.     int i, result, errs;
  879.     LineList globFiles;
  880.     LinePtr globFile;
  881.     char *pagerProg;
  882.  
  883.     if (STREQ(argv[1], "-b") && (gWinInit > 0) && (argc > 2)) {
  884.         /* A hack to let you use the built-in pager like you
  885.          * can with the lpage command.
  886.          */
  887.         pagerProg = NULL;    /* Use built-in */
  888.         i = 2;
  889.     } else {
  890.         if (gPager[0] == '\0') {
  891.             EPrintF("You haven't specified a program to use as a pager.\n");
  892.             EPrintF("You can set this from the preferences screen (prefs command).\n");
  893.             return -1;
  894.         }
  895.         pagerProg = gPager;
  896.         i = 1;
  897.     }
  898.  
  899.     for (errs=0; i<argc; i++) {
  900.         InitLineList(&globFiles);
  901.         RemoteGlob(&globFiles, argv[i], kListNoFlags);
  902.         for (globFile = globFiles.first; globFile != NULL;
  903.             globFile = globFile->next)
  904.         {
  905.             if (pagerProg == NULL)
  906.                 result = DoCat(globFile->line);
  907.             else
  908.                 result = DoPage(globFile->line);
  909.             if (result < 0)
  910.                 --errs;
  911.             if (gXferAbortFlag == SIGINT)
  912.                 break;    /* Don't get rest of files if you interrupted. */
  913.         }
  914.         DisposeLineListContents(&globFiles);
  915.     }
  916.  
  917.     return (errs);
  918. }    /* PageCmd */
  919.  
  920.  
  921.  
  922.  
  923. /* View one or more remote files through your pager. */
  924. int CatCmd(int argc, char **argv)
  925. {
  926.     int i, result, errs;
  927.     LineList globFiles;
  928.     LinePtr globFile;
  929.  
  930.     MultiLineInit();
  931.     for (i=1, errs=0; i<argc; i++) {
  932.         InitLineList(&globFiles);
  933.         RemoteGlob(&globFiles, argv[i], kListNoFlags);
  934.         for (globFile = globFiles.first; globFile != NULL;
  935.             globFile = globFile->next)
  936.         {
  937.             result = DoCat(globFile->line);
  938.             if (result < 0)
  939.                 --errs;
  940.             if (gXferAbortFlag == SIGINT)
  941.                 break;    /* Don't get rest of files if you interrupted. */
  942.             if (argc > 2)
  943.                 MultiLinePrintF("### End of file %s ###\n", globFile->line);
  944.         }
  945.         DisposeLineListContents(&globFiles);
  946.     }
  947.  
  948.     return (errs);
  949. }    /* CatCmd */
  950.