home *** CD-ROM | disk | FTP | other *** search
/ Big Green CD 8 / BGCD_8_Dev.iso / NEXTSTEP / UNIX / Networking / ncftp-2.4.2-MIHS / src / Cmdline.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-10-21  |  15.5 KB  |  580 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 sigNumUNUSED */ void)
  236. {
  237.     DebugMsg("\n*Broken Pipe*\n");
  238.     SIGNAL(SIGPIPE, SigPipeExecCmd);
  239. }    /* SigPipeExecCmd */
  240.  
  241.  
  242.  
  243. /*ARGSUSED*/
  244. static void 
  245. SigIntExecCmd(/* int sigNumUNUSED */ void)
  246. {
  247.     EPrintF("\n*Command Interrupted*\n");
  248.     SIGNAL(SIGINT, SigIntExecCmd);
  249.     alarm(0);
  250.     longjmp(gCommandJmp, 1);
  251. }    /* SigIntExecCmd */
  252.  
  253.  
  254.  
  255.  
  256. /*ARGSUSED*/
  257. static void
  258. SigIntCmdLoop(/* int sigNumUNUSED */ void)
  259. {
  260.     EPrintF("\n*Interrupt*\n");
  261.     SIGNAL(SIGINT, SigIntCmdLoop);
  262.     alarm(0);
  263.     longjmp(gCmdLoopJmp, 1);
  264. }    /* SigIntCmdLoop */
  265.  
  266.  
  267.  
  268.  
  269. /* Looks for only a command (and not macros) in the name list.
  270.  * If 'wantExactMatch' is zero, then you can 'name' can be
  271.  * a unique abbreviation.
  272.  */
  273. CommandPtr GetCommand(char *name, int wantExactMatch)
  274. {
  275.     CMNamePtr cm;
  276.  
  277.     cm = GetCommandOrMacro(name, wantExactMatch);
  278.     if ((cm == kAmbiguousName) || (cm == kNoName)) 
  279.         return ((CommandPtr) NULL);
  280.     else if (!cm->isCmd)
  281.         return ((CommandPtr) NULL);
  282.     return (cm->u.cmd);
  283. }    /* GetCommand */
  284.  
  285.  
  286.  
  287.  
  288. /* Given an entire command line string, parse it up and run it. */
  289. int ExecCommandLine(char *cmdline)
  290. {
  291.     volatile CommandPtr c;
  292.     volatile CMNamePtr cm;
  293.     volatile int err;
  294.     FILE *volatile pipefp;
  295.     volatile CmdLineInfoPtr clp;
  296.     VSig_t si, sp;
  297.     static int depth = 0;
  298.     string str;
  299.  
  300.     sp = (VSig_t) kNoSignalHandler;
  301.     si = (VSig_t) kNoSignalHandler;
  302.     pipefp = NULL;
  303.  
  304.     if (++depth > kRecursionLimit) {
  305.         err = -1;
  306.         Error(kDontPerror,
  307.             "Recursion limit reached.   Did you run a recursive macro?\n");
  308.         goto done;
  309.     }
  310.  
  311.     /*
  312.      * First alloc a bunch of space we'll need.  We have to do it each time
  313.      * through unfortunately, because it is possible for ExecCommandLine to
  314.      * be called recursively.
  315.      */
  316.     MCHK;
  317.     clp = (volatile CmdLineInfoPtr) NEWCMDLINEINFOPTR;
  318.  
  319.     if (clp == (volatile CmdLineInfoPtr) 0) {
  320.         Error(kDontPerror, "Not enough memory to parse command line.\n");
  321.         err = -1;
  322.         goto done;
  323.     }
  324.  
  325.     /* Create the argv[] list and other such stuff. */
  326.     err = (volatile int) MakeArgVector(cmdline, (CmdLineInfoPtr) clp);
  327.  
  328.     DebugMsg("%s\n", cmdline);
  329.     if (err) {
  330.         /* If err was non-zero, MakeArgVector failed and returned
  331.          * an error message for us.
  332.          */
  333.         Error(kDontPerror, "Syntax error: %s.\n", ((CmdLineInfoPtr) clp)->errStr);
  334.         err = -1;
  335.     } else if (((CmdLineInfoPtr) clp)->argCount != 0) {
  336.         err = 0;
  337.         cm = (volatile CMNamePtr)
  338.             GetCommandOrMacro(((CmdLineInfoPtr) clp)->argVector[0], kAbbreviatedMatchAllowed);
  339.         if (cm == (volatile CMNamePtr) kAmbiguousName) {
  340.             Error(kDontPerror, "Ambiguous command or macro name.\n");
  341.             err = -1;
  342.         } else if (cm == (volatile CMNamePtr) kNoName) {
  343.             /* Try implicit cd.  Of course we need to be connected
  344.              * to do this.
  345.              */
  346.             if (gRmtInfo.isUnix) {
  347.                 /* Some servers have a "feature" that also tries other
  348.                  * than the current directory with CWD.
  349.                  */
  350.                 str[0] = '\0';
  351.                 if (((CmdLineInfoPtr) clp)->argVector[0][0] != '/') {
  352.                     /* Try to use the absolute path if at all possible. */
  353.                     STRNCPY(str, gRemoteCWD);
  354.                     STRNCAT(str, "/");
  355.                 }
  356.                 STRNCAT(str, ((CmdLineInfoPtr) clp)->argVector[0]);
  357.             } else {
  358.                 STRNCPY(str, ((CmdLineInfoPtr) clp)->argVector[0]);
  359.             }
  360.             if (!gConnected || (TryQuietChdir(str) < 0)) {
  361.                 Error(kDontPerror, "Invalid command.\n");
  362.                 err = -1;
  363.             }
  364.         } else {
  365.             /* We have something we can run. */
  366.             if (!((CMNamePtr) cm)->isCmd) {
  367.                 /* Do a macro. */
  368.                 ExecuteMacro(((CMNamePtr) cm)->u.mac, ((CmdLineInfoPtr) clp)->argCount, ((CmdLineInfoPtr) clp)->argVector);
  369.             } else {
  370.                 /* Sorry about all these bloody casts, but people complain
  371.                  * to me about compiler warnings.
  372.                  */
  373.  
  374.                 /* We have a command. */
  375.                 c = (volatile CommandPtr) ((CMNamePtr) cm)->u.cmd;
  376.                 if ((((CommandPtr) c)->maxargs != kNoMax) && ((((CmdLineInfoPtr) clp)->argCount - 1) > ((CommandPtr) c)->maxargs)) {
  377.                     PrintCmdUsage((CommandPtr) c);
  378.                     err = -1;
  379.                 } else if ((((CommandPtr) c)->minargs != kNoMax) && ((((CmdLineInfoPtr) clp)->argCount - 1) < ((CommandPtr) c)->minargs)) {
  380.                     PrintCmdUsage((CommandPtr) c);
  381.                     err = -1;
  382.                 } else if (((((CommandPtr) c)->flags & kCmdMustBeConnected) != 0) && (gConnected == 0)) {
  383.                     Error(kDontPerror, "Not connected.\n");
  384.                     err = -1;
  385.                 } else if (((((CommandPtr) c)->flags & kCmdMustBeDisconnected) != 0) && (gConnected == 1)) {
  386.                     Error(kDontPerror, "You must close the connection first before doing this command.\n");
  387.                     err = -1;
  388.                 } else {
  389.                     if ((((CommandPtr) c)->flags & kCmdWaitMsg) != 0) {
  390.                         SetBar(NULL, "RUNNING", NULL, -1, 1);
  391.                     }
  392.                     /* Run our command finally. */
  393.                     if (setjmp(gCommandJmp)) {
  394.                         /* Command was interrupted. */
  395.                         (void) SIGNAL(SIGINT, SIG_IGN);
  396.                         (void) SIGNAL(SIGPIPE, SIG_IGN);
  397.                     } else {
  398.                         si = (volatile Sig_t) SIGNAL(SIGINT, SIG_IGN);
  399.                         sp = (volatile Sig_t) SIGNAL(SIGPIPE, SigPipeExecCmd);
  400.                 
  401.                         /* Make a copy of the real stdout stream, so we can restore
  402.                          * it after the command finishes.
  403.                          */
  404.                         ((CmdLineInfoPtr) clp)->savedStdout = gStdout;
  405.                         ((CmdLineInfoPtr) clp)->outFile = -1;
  406.                         
  407.                         /* Don't > or | if we this command is the shell
  408.                          * command (!), or any other command that specifies
  409.                          * the kCmdNoRedirect flag.
  410.                          */
  411.                         if (!(((CommandPtr) c)->flags & kCmdNoRedirect)) {
  412.                             /* Open the output file if the user supplied one.
  413.                              * This file can be something being redirected into,
  414.                              * such as ">outfile" or a file to pipe output into,
  415.                              * such as "| wc."
  416.                              */
  417.                             if (*((CmdLineInfoPtr) clp)->outFileName) {
  418.                                 if (!((CmdLineInfoPtr) clp)->isAppend)
  419.                                     ((CmdLineInfoPtr) clp)->outFile = open(((CmdLineInfoPtr) clp)->outFileName,
  420.                                         O_WRONLY | O_TRUNC | O_CREAT,
  421.                                         S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
  422.                                 else
  423.                                     ((CmdLineInfoPtr) clp)->outFile = open(((CmdLineInfoPtr) clp)->outFileName,
  424.                                         O_WRONLY | O_APPEND | O_CREAT);
  425.  
  426.                                 if (((CmdLineInfoPtr) clp)->outFile == -1) {
  427.                                     Error(kDoPerror, "Could not open %s for writing.\n", ((CmdLineInfoPtr) clp)->outFileName);
  428.                                     err = -1;
  429.                                     goto done;
  430.                                 }
  431.                             } else if ((*((CmdLineInfoPtr) clp)->pipeCmdLine) && !(((CommandPtr) c)->flags & kCmdDelayPipe)) {
  432.  
  433.                                 DebugMsg("|: '%s'\n", ((CmdLineInfoPtr) clp)->pipeCmdLine);
  434.                                 pipefp = (FILE *) POpen(((CmdLineInfoPtr) clp)->pipeCmdLine, "w", 0);
  435.                                 if (pipefp == NULL) {
  436.                                     Error(kDoPerror, "Could not pipe out to: %s\n", ((CmdLineInfoPtr) clp)->pipeCmdLine);
  437.                                     err = -1;
  438.                                     goto done;
  439.                                 }
  440.                                 ((CmdLineInfoPtr) clp)->outFile = fileno((FILE *) pipefp);
  441.                             }
  442.                         }
  443.                         if (((CmdLineInfoPtr) clp)->outFile != -1) {
  444.                             /* Replace stdout with the FILE pointer we want to
  445.                              * write to, so things like PrintF will print to
  446.                              * the file instead of the screen.
  447.                              */
  448.                             gStdout = ((CmdLineInfoPtr) clp)->outFile;
  449.                         }
  450.                         (void) SIGNAL(SIGINT, SigIntExecCmd);
  451.                         err = (*((CommandPtr) c)->proc) (((CmdLineInfoPtr) clp)->argCount, ((CmdLineInfoPtr) clp)->argVector);
  452.                         (void) SIGNAL(SIGINT, SIG_IGN);
  453.                         (void) SIGNAL(SIGPIPE, SIG_IGN);
  454.                         if (err == kUsageErr)
  455.                             PrintCmdUsage((CommandPtr) c);
  456.                     }
  457.         
  458.                     /* We will clean up the mess now.  The command itself may
  459.                      * have opened a pipe and done the initial setup, but
  460.                      * it still depends on us to close everything.
  461.                      */
  462.                     if (((CmdLineInfoPtr) clp)->outFile != -1) {
  463.                         /* We've run the command, and now it's time to cleanup
  464.                          * our mess.  We close the output file we were writing
  465.                          * to, and replace the stdout variable with the real
  466.                          * stdout stream.
  467.                          */
  468.                         gStdout = ((CmdLineInfoPtr) clp)->savedStdout;
  469.                         if (*((CmdLineInfoPtr) clp)->outFileName)
  470.                             (void) close(((CmdLineInfoPtr) clp)->outFile);
  471.                         else if (pipefp != NULL)
  472.                             (void) pclose((FILE *) pipefp);
  473.                     }
  474.                     /* End if we tried to run a command. */
  475.                 }
  476.                 /* End if we had a command. */
  477.             }
  478.             /* End if we had a macro or command to try. */
  479.         }
  480.         /* End if we had atleast one argument. */
  481.     }
  482. done:
  483.     --depth;
  484.     if (clp != (volatile CmdLineInfoPtr) 0)
  485.         free(clp);
  486.     if (si != (VSig_t) kNoSignalHandler)
  487.         (void) SIGNAL(SIGINT, si);
  488.     if (sp != (VSig_t) kNoSignalHandler)
  489.         (void) SIGNAL(SIGPIPE, sp);
  490.     return err;
  491. }                                       /* ExecCommandLine */
  492.  
  493.  
  494.  
  495.  
  496. /* Look for commands in the FILE pointer supplied, running each
  497.  * one in turn.
  498.  */
  499. void RunScript(FILE *fp)
  500. {
  501.     char line[256];
  502.  
  503.     while (!gDoneApplication) {
  504.         if (FGets(line, sizeof(line), fp) == NULL)
  505.             break;    /* Done. */
  506.         ExecCommandLine(line);
  507.     }
  508. }    /* RunScript */
  509.  
  510.  
  511.  
  512.  
  513. void RunStartupScript(void)
  514. {
  515.     FILE *fp;
  516.     longstring path;
  517.     
  518.     (void) OurDirectoryPath(path, sizeof(path), kStartupScript);
  519.     fp = fopen(path, "r");
  520.     if (fp != NULL) {
  521.         RunScript(fp);
  522.         fclose(fp);
  523.     }
  524. }    /* RunStartupScript */
  525.  
  526.  
  527.  
  528.  
  529. void CommandShell(void)
  530. {
  531.     char line[256];
  532.     time_t cmdStartTime, cmdStopTime;
  533.  
  534.     /* We now set gEventNumber to 1, meaning that we have entered the
  535.      * interactive command shell.  Some commands check gEventNumber
  536.      * against 0, meaning that we hadn't entered the shell yet.
  537.      */
  538.     gEventNumber = 1L;
  539.  
  540.     if (setjmp(gCmdLoopJmp)) {
  541.         /* Interrupted. */
  542.         ++gNumInterruptions;
  543.         if (gNumInterruptions == 3)
  544.             EPrintF("(Interrupt the program again to kill program.)\n");
  545.         else if (gNumInterruptions > 3)
  546.             gDoneApplication = 1;
  547.         DebugMsg("\nReturning to top level.\n");
  548.     }
  549.  
  550.     while (!gDoneApplication) {
  551.         (void) SIGNAL(SIGPIPE, SIG_IGN);
  552.         (void) SIGNAL(SIGINT, SigIntCmdLoop);
  553.  
  554.         /* If the user logged out and left us in the background,
  555.          * quit, unless a script is running.
  556.          */
  557.         if (!gDoingScript && !UserLoggedIn())
  558.             break;        /* User hung up. */
  559.         (void) CheckNewMail();
  560.         if (Gets(line, sizeof(line)) == NULL)
  561.             break;    /* Done. */
  562.         if (gWinInit) {
  563.             if (gBlankLines)
  564.                 PrintF("\n");
  565.             BoldPrintF("> %s\n", line);
  566.             if (gBlankLines)
  567.                 PrintF("\n");
  568.         }
  569.         FlushListWindow();
  570.  
  571.         time(&cmdStartTime);
  572.         ExecCommandLine(line);
  573.         time(&cmdStopTime);
  574.         if ((int) (cmdStopTime - cmdStartTime) > kBeepAfterCmdTime)
  575.             Beep(1);
  576.  
  577.         ++gEventNumber;
  578.     }
  579. }    /* CommandShell */
  580.