home *** CD-ROM | disk | FTP | other *** search
/ Geek Gadgets 1 / ADE-1.bin / ade-dist / ncftp-2.3.0-base.tgz / ncftp-2.3.0-base.tar / contrib / ncftp / Cmdline.c < prev    next >
C/C++ Source or Header  |  1995-11-26  |  16KB  |  581 lines

  1. /* Cmdline.c
  2.  *
  3.  * Purpose: Read and execute user input lines from the program shell.
  4.  */
  5.  
  6. #include "Sys.h"
  7.  
  8. #include <signal.h>
  9. #include <setjmp.h>
  10.  
  11. #include "Util.h"
  12. #include "Cmdline.h"
  13. #include "Cmds.h"
  14. #include "Main.h"
  15. #include "MakeArgv.h"
  16. #include "Open.h"
  17. #include "Bookmark.h"
  18.  
  19. static int CMSortCmp(CMNamePtr, CMNamePtr);
  20. static int CMExactSearchCmp(char *, CMNamePtr);
  21. static int CMSubSearchCmp(char *, CMNamePtr);
  22.  
  23. /* This is the sorted array of commands and macros.  When a user types
  24.  * something on the command line, we look to see if we have a match
  25.  * in this list.
  26.  */
  27. CMNamePtr gNameList = (CMNamePtr)0;
  28.  
  29. /* Number of commands and macros in the NameList. */
  30. int gNumNames;
  31.  
  32. /* Upon receipt of a signal during execution of a command, we jump here. */
  33. jmp_buf gCommandJmp;
  34.  
  35. /* Upon receipt of a signal while waiting for input, we jump here. */
  36. jmp_buf gCmdLoopJmp;
  37.  
  38. /* We have our commands write to our own "stdout" so we can have our stdout
  39.  * point to a file, and at other times have it go to the screen.
  40.  *
  41.  * So gRealStdout is a copy of the real "stdout" stream, and gStdout is
  42.  * our stream, which may or may not be writing to the screen.
  43.  */
  44. int gRealStdout;
  45. int gStdout;
  46. FILE *gStdoutFile;
  47.  
  48. /* We keep running the command line interpreter until gDoneApplication
  49.  * is non-zero.
  50.  */
  51. int gDoneApplication = 0;
  52.  
  53. /* Track how many times they use ^C. */
  54. int gNumInterruptions = 0;
  55.  
  56. /* Keep a count of the number of commands the user has entered. */
  57. long gEventNumber = 0L;
  58.  
  59. /* If using visual mode, this controls how to insert extra blank lines
  60.  * before/after commands.
  61.  */
  62. int gBlankLines = 1;
  63.  
  64. extern int gNumCommands;
  65. extern int gNumGlobalMacros;
  66. extern MacroNodePtr gFirstMacro;
  67. extern int gConnected, gWinInit;
  68. extern string gHost;
  69. extern Command gCommands[];
  70. extern UserInfo gUserInfo;
  71. extern Bookmark gRmtInfo;
  72. extern longstring gRemoteCWD;
  73. extern int gIsFromTTY, gDoingScript, gIsToTTY;
  74.  
  75.  
  76. /* This is used as the comparison function when we sort the name list. */
  77. static int CMSortCmp(CMNamePtr a, CMNamePtr b)
  78. {
  79.     return (strcmp((*a).name, (*b).name));
  80. }    /* CMSortCmp */
  81.  
  82.  
  83.  
  84.  
  85. /* We make a big list of the names of the all the standard commands, plus
  86.  * the names of all the macros read in.  Then when the user
  87.  * types a command name on the command line, we search this list and see
  88.  * what the name matches (either a command, a macro, or nothing).
  89.  */
  90.  
  91. int InitCommandAndMacroNameList(void)
  92. {
  93.     Command            *c;
  94.     CMNamePtr        canp;
  95.     MacroNodePtr    mnp;
  96.     int i;
  97.  
  98.     /* Free an old list, in case you want to re-make it. */
  99.     if (gNameList != (CMNamePtr)0)
  100.         free(gNameList);
  101.  
  102.     gNumNames = gNumGlobalMacros + gNumCommands;    
  103.     gNameList = (CMNamePtr) calloc((size_t)gNumNames, sizeof (CMName));
  104.     if (gNameList == NULL)
  105.         OutOfMemory();
  106.  
  107.     i = 0;
  108.     for (c = gCommands, canp = gNameList; i < gNumCommands ; c++, canp++) {
  109.         canp->name = c->name;
  110.         canp->isCmd = 1;
  111.         canp->u.cmd = c;
  112.         ++i;
  113.     }
  114.     
  115.     for (mnp = gFirstMacro; mnp != NULL; mnp = mnp->next, canp++) {
  116.         canp->name = mnp->name;
  117.         canp->isCmd = 0;
  118.         canp->u.mac = mnp;
  119.         ++i;
  120.     }
  121.     
  122.     /* Now sort the list so we can use bsearch later. */
  123.     QSORT(gNameList, gNumNames, sizeof(CMName), CMSortCmp);
  124.  
  125.     return (0);
  126. }    /* InitCommandAndMacroNameList */
  127.  
  128.  
  129.  
  130.  
  131. /* This is used as the comparison function when we lookup something
  132.  * in the name list, and when we want an exact match.
  133.  */
  134. static int CMExactSearchCmp(char *key, CMNamePtr b)
  135. {
  136.     return (strcmp(key, (*b).name));
  137. }    /* CMExactSearchCmp */
  138.  
  139.  
  140.  
  141.  
  142. /* This is used as the comparison function when we lookup something
  143.  * in the name list, and when the key can be just the first few
  144.  * letters of one or more commands.  So a key of "qu" might would match
  145.  * "quit" and "quote" for example.
  146.  */
  147. static int CMSubSearchCmp(char *key, CMNamePtr a)
  148. {
  149.     register char *kcp, *cp;
  150.     int d;
  151.  
  152.     for (cp = (*a).name, kcp = key; ; ) {
  153.         if (*kcp == 0)
  154.             return 0;
  155.         d = *kcp++ - *cp++;
  156.         if (d)
  157.             return d;
  158.     }
  159. }    /* CMSubSearchCmp */
  160.  
  161.  
  162. /* This returns a pointer to a CAName, if the name supplied was long
  163.  * enough to be a unique name.  We return a 0 CANamePtr if we did not
  164.  * find any matches, a -1 CANamePtr if we found more than one match,
  165.  * or the unique CANamePtr.
  166.  */
  167. CMNamePtr GetCommandOrMacro(char *name, int wantExactMatch)
  168. {
  169.     CMNamePtr canp, canp2;
  170.  
  171.     /* First check for an exact match.  Otherwise if you if asked for
  172.      * 'cd', it would match both 'cd' and 'cdup' and return an
  173.      * ambiguous name error, despite having the exact name for 'cd.'
  174.      */
  175.     canp = (CMNamePtr) BSEARCH(name, gNameList, gNumNames, sizeof(CMName), CMExactSearchCmp);
  176.  
  177.     if (canp == kNoName && !wantExactMatch) {
  178.         /* Now see if the user typed an abbreviation unique enough
  179.          * to match only one name in the list.
  180.          */
  181.         canp = (CMNamePtr) BSEARCH(name, gNameList, gNumNames, sizeof(CMName), CMSubSearchCmp);
  182.         
  183.         if (canp != kNoName) {
  184.             /* Check the entry above us and see if the name we're looking
  185.              * for would match that, too.
  186.              */
  187.             if (canp != &gNameList[0]) {
  188.                 canp2 = canp - 1;
  189.                 if (CMSubSearchCmp(name, canp2) == 0)
  190.                     return kAmbiguousName;
  191.             }
  192.             /* Check the entry below us and see if the name we're looking
  193.              * for would match that one.
  194.              */
  195.             if (canp != &gNameList[gNumNames - 1]) {
  196.                 canp2 = canp + 1;
  197.                 if (CMSubSearchCmp(name, canp2) == 0)
  198.                     return kAmbiguousName;
  199.             }
  200.         }
  201.     }
  202.     return canp;
  203. }                                       /* GetCommandOrMacro */
  204.  
  205.  
  206.  
  207.  
  208. /* Print the help string for the command specified. */
  209. void PrintCmdHelp(CommandPtr c)
  210. {
  211.     PrintF("%s: %s.\n",
  212.                   c->name,
  213.                   c->help
  214.     );
  215. }                                       /* PrintCmdHelp */
  216.  
  217.  
  218.  
  219.  
  220. /* Print the usage string for the command specified. */
  221. void PrintCmdUsage(CommandPtr c)
  222. {
  223.     if (c->usage != NULL)
  224.         PrintF("Usage: %s %s\n",
  225.                       c->name,
  226.                       c->usage
  227.         );
  228. }                                       /* PrintCmdUsage */
  229.  
  230.  
  231.  
  232.  
  233. /*ARGSUSED*/
  234. static void 
  235. SigPipeExecCmd(int sigNum)
  236. {
  237.     DebugMsg("\n*Broken Pipe*\n");
  238.     SIGNAL(SIGPIPE, SigPipeExecCmd);
  239.     /* alarm(0); */
  240.     /* longjmp(gCommandJmp, 1); */
  241. }    /* SigPipeExecCmd */
  242.  
  243.  
  244.  
  245. /*ARGSUSED*/
  246. static void 
  247. SigIntExecCmd(int sigNum)
  248. {
  249.     EPrintF("\n*Command Interrupted*\n");
  250.     SIGNAL(SIGINT, SigIntExecCmd);
  251.     alarm(0);
  252.     longjmp(gCommandJmp, 1);
  253. }    /* SigIntExecCmd */
  254.  
  255.  
  256.  
  257.  
  258. /*ARGSUSED*/
  259. static void
  260. SigIntCmdLoop(int sigNum)
  261. {
  262.     EPrintF("\n*Interrupt*\n");
  263.     SIGNAL(SIGINT, SigIntCmdLoop);
  264.     alarm(0);
  265.     longjmp(gCmdLoopJmp, 1);
  266. }    /* SigIntCmdLoop */
  267.  
  268.  
  269.  
  270.  
  271. /* Looks for only a command (and not macros) in the name list.
  272.  * If 'wantExactMatch' is zero, then you can 'name' can be
  273.  * a unique abbreviation.
  274.  */
  275. CommandPtr GetCommand(char *name, int wantExactMatch)
  276. {
  277.     CMNamePtr cm;
  278.  
  279.     cm = GetCommandOrMacro(name, wantExactMatch);
  280.     if ((cm == kAmbiguousName) || (cm == kNoName)) 
  281.         return ((CommandPtr) NULL);
  282.     else if (!cm->isCmd)
  283.         return ((CommandPtr) NULL);
  284.     return (cm->u.cmd);
  285. }    /* GetCommand */
  286.  
  287.  
  288.  
  289.  
  290. /* Given an entire command line string, parse it up and run it. */
  291. int ExecCommandLine(char *cmdline)
  292. {
  293.     volatile CommandPtr c;
  294.     volatile CMNamePtr cm;
  295.     volatile int err;
  296.     volatile FILE *pipefp;
  297.     volatile CmdLineInfoPtr clp;
  298.     VSig_t si, sp;
  299.     static int depth = 0;
  300.     string str;
  301.  
  302.     sp = (VSig_t) kNoSignalHandler;
  303.     si = (VSig_t) kNoSignalHandler;
  304.  
  305.     if (++depth > kRecursionLimit) {
  306.         err = -1;
  307.         Error(kDontPerror,
  308.             "Recursion limit reached.   Did you run a recursive macro?\n");
  309.         goto done;
  310.     }
  311.  
  312.     /*
  313.      * First alloc a bunch of space we'll need.  We have to do it each time
  314.      * through unfortunately, because it is possible for ExecCommandLine to
  315.      * be called recursively.
  316.      */
  317.     MCHK;
  318.     clp = (volatile CmdLineInfoPtr) NEWCMDLINEINFOPTR;
  319.  
  320.     if (clp == (volatile CmdLineInfoPtr) 0) {
  321.         Error(kDontPerror, "Not enough memory to parse command line.\n");
  322.         err = -1;
  323.         goto done;
  324.     }
  325.  
  326.     /* Create the argv[] list and other such stuff. */
  327.     err = (volatile int) MakeArgVector(cmdline, (CmdLineInfoPtr) clp);
  328.  
  329.     DebugMsg("%s\n", cmdline);
  330.     if (err) {
  331.         /* If err was non-zero, MakeArgVector failed and returned
  332.          * an error message for us.
  333.          */
  334.         Error(kDontPerror, "Syntax error: %s.\n", ((CmdLineInfoPtr) clp)->errStr);
  335.         err = -1;
  336.     } else if (((CmdLineInfoPtr) clp)->argCount != 0) {
  337.         err = 0;
  338.         cm = (volatile CMNamePtr)
  339.             GetCommandOrMacro(((CmdLineInfoPtr) clp)->argVector[0], kAbbreviatedMatchAllowed);
  340.         if (cm == (volatile CMNamePtr) kAmbiguousName) {
  341.             Error(kDontPerror, "Ambiguous command or macro name.\n");
  342.             err = -1;
  343.         } else if (cm == (volatile CMNamePtr) kNoName) {
  344.             /* Try implicit cd.  Of course we need to be connected
  345.              * to do this.
  346.              */
  347.             if (gRmtInfo.isUnix) {
  348.                 /* Some servers have a "feature" that also tries other
  349.                  * than the current directory with CWD.
  350.                  */
  351.                 str[0] = '\0';
  352.                 if (((CmdLineInfoPtr) clp)->argVector[0][0] != '/') {
  353.                     /* Try to use the absolute path if at all possible. */
  354.                     STRNCPY(str, gRemoteCWD);
  355.                     STRNCAT(str, "/");
  356.                 }
  357.                 STRNCAT(str, ((CmdLineInfoPtr) clp)->argVector[0]);
  358.             } else {
  359.                 STRNCPY(str, ((CmdLineInfoPtr) clp)->argVector[0]);
  360.             }
  361.             if (!gConnected || (TryQuietChdir(str) < 0)) {
  362.                 Error(kDontPerror, "Invalid command.\n");
  363.                 err = -1;
  364.             }
  365.         } else {
  366.             /* We have something we can run. */
  367.             if (!((CMNamePtr) cm)->isCmd) {
  368.                 /* Do a macro. */
  369.                 ExecuteMacro(((CMNamePtr) cm)->u.mac, ((CmdLineInfoPtr) clp)->argCount, ((CmdLineInfoPtr) clp)->argVector);
  370.             } else {
  371.                 /* Sorry about all these bloody casts, but people complain
  372.                  * to me about compiler warnings.
  373.                  */
  374.  
  375.                 /* We have a command. */
  376.                 c = (volatile CommandPtr) ((CMNamePtr) cm)->u.cmd;
  377.                 if ((((CommandPtr) c)->maxargs != kNoMax) && ((((CmdLineInfoPtr) clp)->argCount - 1) > ((CommandPtr) c)->maxargs)) {
  378.                     PrintCmdUsage((CommandPtr) c);
  379.                     err = -1;
  380.                 } else if ((((CommandPtr) c)->minargs != kNoMax) && ((((CmdLineInfoPtr) clp)->argCount - 1) < ((CommandPtr) c)->minargs)) {
  381.                     PrintCmdUsage((CommandPtr) c);
  382.                     err = -1;
  383.                 } else if (((((CommandPtr) c)->flags & kCmdMustBeConnected) != 0) && (gConnected == 0)) {
  384.                     Error(kDontPerror, "Not connected.\n");
  385.                     err = -1;
  386.                 } else if (((((CommandPtr) c)->flags & kCmdMustBeDisconnected) != 0) && (gConnected == 1)) {
  387.                     Error(kDontPerror, "You must close the connection first before doing this command.\n");
  388.                     err = -1;
  389.                 } else {
  390.                     if ((((CommandPtr) c)->flags & kCmdWaitMsg) != 0) {
  391.                         SetBar(NULL, "RUNNING", NULL, -1, 1);
  392.                     }
  393.                     /* Run our command finally. */
  394.                     if (setjmp(gCommandJmp)) {
  395.                         /* Command was interrupted. */
  396.                         (void) SIGNAL(SIGINT, SIG_IGN);
  397.                         (void) SIGNAL(SIGPIPE, SIG_IGN);
  398.                     } else {
  399.                         si = (volatile Sig_t) SIGNAL(SIGINT, SIG_IGN);
  400.                         sp = (volatile Sig_t) SIGNAL(SIGPIPE, SigPipeExecCmd);
  401.                 
  402.                         /* Make a copy of the real stdout stream, so we can restore
  403.                          * it after the command finishes.
  404.                          */
  405.                         ((CmdLineInfoPtr) clp)->savedStdout = gStdout;
  406.                         ((CmdLineInfoPtr) clp)->outFile = -1;
  407.                         
  408.                         /* Don't > or | if we this command is the shell
  409.                          * command (!), or any other command that specifies
  410.                          * the kCmdNoRedirect flag.
  411.                          */
  412.                         if (!(((CommandPtr) c)->flags & kCmdNoRedirect)) {
  413.                             /* Open the output file if the user supplied one.
  414.                              * This file can be something being redirected into,
  415.                              * such as ">outfile" or a file to pipe output into,
  416.                              * such as "| wc."
  417.                              */
  418.                             if (*((CmdLineInfoPtr) clp)->outFileName) {
  419.                                 if (!((CmdLineInfoPtr) clp)->isAppend)
  420.                                     ((CmdLineInfoPtr) clp)->outFile = open(((CmdLineInfoPtr) clp)->outFileName,
  421.                                         O_WRONLY | O_TRUNC | O_CREAT,
  422.                                         S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
  423.                                 else
  424.                                     ((CmdLineInfoPtr) clp)->outFile = open(((CmdLineInfoPtr) clp)->outFileName,
  425.                                         O_WRONLY | O_APPEND | O_CREAT);
  426.  
  427.                                 if (((CmdLineInfoPtr) clp)->outFile == -1) {
  428.                                     Error(kDoPerror, "Could not open %s for writing.\n", ((CmdLineInfoPtr) clp)->outFileName);
  429.                                     err = -1;
  430.                                     goto done;
  431.                                 }
  432.                             } else if ((*((CmdLineInfoPtr) clp)->pipeCmdLine) && !(((CommandPtr) c)->flags & kCmdDelayPipe)) {
  433.  
  434.                                 DebugMsg("|: '%s'\n", ((CmdLineInfoPtr) clp)->pipeCmdLine);
  435.                                 pipefp = (FILE *) POpen(((CmdLineInfoPtr) clp)->pipeCmdLine, "w", 0);
  436.                                 if (pipefp == NULL) {
  437.                                     Error(kDoPerror, "Could not pipe out to: %s\n", ((CmdLineInfoPtr) clp)->pipeCmdLine);
  438.                                     err = -1;
  439.                                     goto done;
  440.                                 }
  441.                                 ((CmdLineInfoPtr) clp)->outFile = fileno((FILE *) pipefp);
  442.                             }
  443.                         }
  444.                         if (((CmdLineInfoPtr) clp)->outFile != -1) {
  445.                             /* Replace stdout with the FILE pointer we want to
  446.                              * write to, so things like PrintF will print to
  447.                              * the file instead of the screen.
  448.                              */
  449.                             gStdout = ((CmdLineInfoPtr) clp)->outFile;
  450.                         }
  451.                         (void) SIGNAL(SIGINT, SigIntExecCmd);
  452.                         err = (*((CommandPtr) c)->proc) (((CmdLineInfoPtr) clp)->argCount, ((CmdLineInfoPtr) clp)->argVector);
  453.                         (void) SIGNAL(SIGINT, SIG_IGN);
  454.                         (void) SIGNAL(SIGPIPE, SIG_IGN);
  455.                         if (err == kUsageErr)
  456.                             PrintCmdUsage((CommandPtr) c);
  457.                     }
  458.         
  459.                     /* We will clean up the mess now.  The command itself may
  460.                      * have opened a pipe and done the initial setup, but
  461.                      * it still depends on us to close everything.
  462.                      */
  463.                     if (((CmdLineInfoPtr) clp)->outFile != -1) {
  464.                         /* We've run the command, and now it's time to cleanup
  465.                          * our mess.  We close the output file we were writing
  466.                          * to, and replace the stdout variable with the real
  467.                          * stdout stream.
  468.                          */
  469.                         gStdout = ((CmdLineInfoPtr) clp)->savedStdout;
  470.                         if (*((CmdLineInfoPtr) clp)->outFileName)
  471.                             (void) close(((CmdLineInfoPtr) clp)->outFile);
  472.                         else
  473.                             (void) pclose((FILE *) pipefp);
  474.                     }
  475.                     /* End if we tried to run a command. */
  476.                 }
  477.                 /* End if we had a command. */
  478.             }
  479.             /* End if we had a macro or command to try. */
  480.         }
  481.         /* End if we had atleast one argument. */
  482.     }
  483. done:
  484.     --depth;
  485.     if (clp != (volatile CmdLineInfoPtr) 0)
  486.         free(clp);
  487.     if (si != (VSig_t) kNoSignalHandler)
  488.         (void) SIGNAL(SIGINT, si);
  489.     if (sp != (VSig_t) kNoSignalHandler)
  490.         (void) SIGNAL(SIGPIPE, sp);
  491.     return err;
  492. }                                       /* ExecCommandLine */
  493.  
  494.  
  495.  
  496.  
  497. /* Look for commands in the FILE pointer supplied, running each
  498.  * one in turn.
  499.  */
  500. void RunScript(FILE *fp)
  501. {
  502.     char line[256];
  503.  
  504.     while (!gDoneApplication) {
  505.         if (FGets(line, sizeof(line), fp) == NULL)
  506.             break;    /* Done. */
  507.         ExecCommandLine(line);
  508.     }
  509. }    /* RunScript */
  510.  
  511.  
  512.  
  513.  
  514. void RunStartupScript(void)
  515. {
  516.     FILE *fp;
  517.     longstring path;
  518.     
  519.     (void) OurDirectoryPath(path, sizeof(path), kStartupScript);
  520.     fp = fopen(path, "r");
  521.     if (fp != NULL) {
  522.         RunScript(fp);
  523.         fclose(fp);
  524.     }
  525. }    /* RunStartupScript */
  526.  
  527.  
  528.  
  529.  
  530. void CommandShell(void)
  531. {
  532.     char line[256];
  533.     time_t cmdStartTime, cmdStopTime;
  534.  
  535.     /* We now set gEventNumber to 1, meaning that we have entered the
  536.      * interactive command shell.  Some commands check gEventNumber
  537.      * against 0, meaning that we hadn't entered the shell yet.
  538.      */
  539.     gEventNumber = 1L;
  540.  
  541.     if (setjmp(gCmdLoopJmp)) {
  542.         /* Interrupted. */
  543.         ++gNumInterruptions;
  544.         if (gNumInterruptions == 3)
  545.             EPrintF("(Interrupt the program again to kill program.)\n");
  546.         else if (gNumInterruptions > 3)
  547.             gDoneApplication = 1;
  548.         DebugMsg("\nReturning to top level.\n");
  549.     }
  550.  
  551.     while (!gDoneApplication) {
  552.         (void) SIGNAL(SIGPIPE, SIG_IGN);
  553.         (void) SIGNAL(SIGINT, SigIntCmdLoop);
  554.  
  555.         /* If the user logged out and left us in the background,
  556.          * quit, unless a script is running.
  557.          */
  558.         if (!gDoingScript && !UserLoggedIn())
  559.             break;        /* User hung up. */
  560.         (void) CheckNewMail();
  561.         if (Gets(line, sizeof(line)) == NULL)
  562.             break;    /* Done. */
  563.         if (gWinInit) {
  564.             if (gBlankLines)
  565.                 PrintF("\n");
  566.             BoldPrintF("> %s\n", line);
  567.             if (gBlankLines)
  568.                 PrintF("\n");
  569.         }
  570.         FlushListWindow();
  571.  
  572.         time(&cmdStartTime);
  573.         ExecCommandLine(line);
  574.         time(&cmdStopTime);
  575.         if ((int) (cmdStopTime - cmdStartTime) > kBeepAfterCmdTime)
  576.             Beep(1);
  577.  
  578.         ++gEventNumber;
  579.     }
  580. }    /* CommandShell */
  581.