Logo Search packages:      
Sourcecode: ncftp2 version File versions  Download package

Cmdline.c

/* Cmdline.c
 *
 * Purpose: Read and execute user input lines from the program shell.
 */

#include "Sys.h"

#include <signal.h>
#include <setjmp.h>

#include "Util.h"
#include "Cmdline.h"
#include "Cmds.h"
#include "Main.h"
#include "MakeArgv.h"
#include "Open.h"
#include "Bookmark.h"

static int CMSortCmp(CMNamePtr, CMNamePtr);
static int CMExactSearchCmp(char *, CMNamePtr);
static int CMSubSearchCmp(char *, CMNamePtr);

/* This is the sorted array of commands and macros.  When a user types
 * something on the command line, we look to see if we have a match
 * in this list.
 */
CMNamePtr gNameList = (CMNamePtr)0;

/* Number of commands and macros in the NameList. */
int gNumNames;

/* Upon receipt of a signal during execution of a command, we jump here. */
jmp_buf gCommandJmp;

/* Upon receipt of a signal while waiting for input, we jump here. */
jmp_buf gCmdLoopJmp;

/* We have our commands write to our own "stdout" so we can have our stdout
 * point to a file, and at other times have it go to the screen.
 *
 * So gRealStdout is a copy of the real "stdout" stream, and gStdout is
 * our stream, which may or may not be writing to the screen.
 */
int gRealStdout;
int gStdout;
FILE *gStdoutFile;

/* We keep running the command line interpreter until gDoneApplication
 * is non-zero.
 */
int gDoneApplication = 0;

/* Track how many times they use ^C. */
int gNumInterruptions = 0;

/* Keep a count of the number of commands the user has entered. */
long gEventNumber = 0L;

/* If using visual mode, this controls how to insert extra blank lines
 * before/after commands.
 */
int gBlankLines = 1;

extern int gNumCommands;
extern int gNumGlobalMacros;
extern MacroNodePtr gFirstMacro;
extern int gConnected, gWinInit;
extern string gHost;
extern Command gCommands[];
extern UserInfo gUserInfo;
extern Bookmark gRmtInfo;
extern longstring gRemoteCWD;
extern int gIsFromTTY, gDoingScript, gIsToTTY;


/* This is used as the comparison function when we sort the name list. */
static int CMSortCmp(CMNamePtr a, CMNamePtr b)
{
      return (strcmp((*a).name, (*b).name));
}     /* CMSortCmp */




/* We make a big list of the names of the all the standard commands, plus
 * the names of all the macros read in.  Then when the user
 * types a command name on the command line, we search this list and see
 * what the name matches (either a command, a macro, or nothing).
 */

int InitCommandAndMacroNameList(void)
{
      Command                 *c;
      CMNamePtr         canp;
      MacroNodePtr      mnp;
      int i;

      /* Free an old list, in case you want to re-make it. */
      if (gNameList != (CMNamePtr)0)
            free(gNameList);

      gNumNames = gNumGlobalMacros + gNumCommands;    
      gNameList = (CMNamePtr) calloc((size_t)gNumNames, sizeof (CMName));
      if (gNameList == NULL)
            OutOfMemory();

      i = 0;
      for (c = gCommands, canp = gNameList; i < gNumCommands ; c++, canp++) {
            canp->name = c->name;
            canp->isCmd = 1;
            canp->u.cmd = c;
            ++i;
      }
      
      for (mnp = gFirstMacro; mnp != NULL; mnp = mnp->next, canp++) {
            canp->name = mnp->name;
            canp->isCmd = 0;
            canp->u.mac = mnp;
            ++i;
      }
      
      /* Now sort the list so we can use bsearch later. */
      QSORT(gNameList, gNumNames, sizeof(CMName), CMSortCmp);

      return (0);
}     /* InitCommandAndMacroNameList */




/* This is used as the comparison function when we lookup something
 * in the name list, and when we want an exact match.
 */
static int CMExactSearchCmp(char *key, CMNamePtr b)
{
      return (strcmp(key, (*b).name));
}     /* CMExactSearchCmp */




/* This is used as the comparison function when we lookup something
 * in the name list, and when the key can be just the first few
 * letters of one or more commands.  So a key of "qu" might would match
 * "quit" and "quote" for example.
 */
static int CMSubSearchCmp(char *key, CMNamePtr a)
{
      register char *kcp, *cp;
      int d;

      for (cp = (*a).name, kcp = key; ; ) {
            if (*kcp == 0)
                  return 0;
            d = *kcp++ - *cp++;
            if (d)
                  return d;
      }
}     /* CMSubSearchCmp */


/* This returns a pointer to a CAName, if the name supplied was long
 * enough to be a unique name.  We return a 0 CANamePtr if we did not
 * find any matches, a -1 CANamePtr if we found more than one match,
 * or the unique CANamePtr.
 */
CMNamePtr GetCommandOrMacro(char *name, int wantExactMatch)
{
      CMNamePtr canp, canp2;

      /* First check for an exact match.  Otherwise if you if asked for
       * 'cd', it would match both 'cd' and 'cdup' and return an
       * ambiguous name error, despite having the exact name for 'cd.'
       */
      canp = (CMNamePtr) BSEARCH(name, gNameList, gNumNames, sizeof(CMName), CMExactSearchCmp);

      if (canp == kNoName && !wantExactMatch) {
            /* Now see if the user typed an abbreviation unique enough
             * to match only one name in the list.
             */
            canp = (CMNamePtr) BSEARCH(name, gNameList, gNumNames, sizeof(CMName), CMSubSearchCmp);
            
            if (canp != kNoName) {
                  /* Check the entry above us and see if the name we're looking
                   * for would match that, too.
                   */
                  if (canp != &gNameList[0]) {
                        canp2 = canp - 1;
                        if (CMSubSearchCmp(name, canp2) == 0)
                              return kAmbiguousName;
                  }
                  /* Check the entry below us and see if the name we're looking
                   * for would match that one.
                   */
                  if (canp != &gNameList[gNumNames - 1]) {
                        canp2 = canp + 1;
                        if (CMSubSearchCmp(name, canp2) == 0)
                              return kAmbiguousName;
                  }
            }
      }
      return canp;
}                                                        /* GetCommandOrMacro */




/* Print the help string for the command specified. */
void PrintCmdHelp(CommandPtr c)
{
      PrintF("%s: %s.\n",
                          c->name,
                          c->help
      );
}                                                        /* PrintCmdHelp */




/* Print the usage string for the command specified. */
void PrintCmdUsage(CommandPtr c)
{
      if (c->usage != NULL)
            PrintF("Usage: %s %s\n",
                                c->name,
                                c->usage
            );
}                                                        /* PrintCmdUsage */




/*ARGSUSED*/
static void 
SigPipeExecCmd(/* int sigNumUNUSED */ void)
{
      DebugMsg("\n*Broken Pipe*\n");
      SIGNAL(SIGPIPE, SigPipeExecCmd);
}     /* SigPipeExecCmd */



/*ARGSUSED*/
static void 
SigIntExecCmd(/* int sigNumUNUSED */ void)
{
      EPrintF("\n*Command Interrupted*\n");
      SIGNAL(SIGINT, SigIntExecCmd);
      alarm(0);
      longjmp(gCommandJmp, 1);
}     /* SigIntExecCmd */




/*ARGSUSED*/
static void
SigIntCmdLoop(/* int sigNumUNUSED */ void)
{
      EPrintF("\n*Interrupt*\n");
      SIGNAL(SIGINT, SigIntCmdLoop);
      alarm(0);
      longjmp(gCmdLoopJmp, 1);
}     /* SigIntCmdLoop */




/* Looks for only a command (and not macros) in the name list.
 * If 'wantExactMatch' is zero, then you can 'name' can be
 * a unique abbreviation.
 */
CommandPtr GetCommand(char *name, int wantExactMatch)
{
      CMNamePtr cm;

      cm = GetCommandOrMacro(name, wantExactMatch);
      if ((cm == kAmbiguousName) || (cm == kNoName)) 
            return ((CommandPtr) NULL);
      else if (!cm->isCmd)
            return ((CommandPtr) NULL);
      return (cm->u.cmd);
}     /* GetCommand */




/* Given an entire command line string, parse it up and run it. */
int ExecCommandLine(char *cmdline)
{
      volatile CommandPtr c;
      volatile CMNamePtr cm;
      volatile int err;
      FILE *volatile pipefp;
      volatile CmdLineInfoPtr clp;
      VSig_t si, sp;
      static int depth = 0;
      string str;

      sp = (VSig_t) kNoSignalHandler;
      si = (VSig_t) kNoSignalHandler;
      pipefp = NULL;

      if (++depth > kRecursionLimit) {
            err = -1;
            Error(kDontPerror,
                  "Recursion limit reached.   Did you run a recursive macro?\n");
            goto done;
      }

      /*
       * First alloc a bunch of space we'll need.  We have to do it each time
       * through unfortunately, because it is possible for ExecCommandLine to
       * be called recursively.
       */
      MCHK;
      clp = (volatile CmdLineInfoPtr) NEWCMDLINEINFOPTR;

      if (clp == (volatile CmdLineInfoPtr) 0) {
            Error(kDontPerror, "Not enough memory to parse command line.\n");
            err = -1;
            goto done;
      }

      /* Create the argv[] list and other such stuff. */
      err = (volatile int) MakeArgVector(cmdline, (CmdLineInfoPtr) clp);

      DebugMsg("%s\n", cmdline);
      if (err) {
            /* If err was non-zero, MakeArgVector failed and returned
             * an error message for us.
             */
            Error(kDontPerror, "Syntax error: %s.\n", ((CmdLineInfoPtr) clp)->errStr);
            err = -1;
      } else if (((CmdLineInfoPtr) clp)->argCount != 0) {
            err = 0;
            cm = (volatile CMNamePtr)
                  GetCommandOrMacro(((CmdLineInfoPtr) clp)->argVector[0], kAbbreviatedMatchAllowed);
            if (cm == (volatile CMNamePtr) kAmbiguousName) {
                  Error(kDontPerror, "Ambiguous command or macro name.\n");
                  err = -1;
            } else if (cm == (volatile CMNamePtr) kNoName) {
                  /* Try implicit cd.  Of course we need to be connected
                   * to do this.
                   */
                  if (gRmtInfo.isUnix) {
                        /* Some servers have a "feature" that also tries other
                         * than the current directory with CWD.
                         */
                        str[0] = '\0';
                        if (((CmdLineInfoPtr) clp)->argVector[0][0] != '/') {
                              /* Try to use the absolute path if at all possible. */
                              STRNCPY(str, gRemoteCWD);
                              STRNCAT(str, "/");
                        }
                        STRNCAT(str, ((CmdLineInfoPtr) clp)->argVector[0]);
                  } else {
                        STRNCPY(str, ((CmdLineInfoPtr) clp)->argVector[0]);
                  }
                  if (!gConnected || (TryQuietChdir(str) < 0)) {
                        Error(kDontPerror, "Invalid command.\n");
                        err = -1;
                  }
            } else {
                  /* We have something we can run. */
                  if (!((CMNamePtr) cm)->isCmd) {
                        /* Do a macro. */
                        ExecuteMacro(((CMNamePtr) cm)->u.mac, ((CmdLineInfoPtr) clp)->argCount, ((CmdLineInfoPtr) clp)->argVector);
                  } else {
                        /* Sorry about all these bloody casts, but people complain
                         * to me about compiler warnings.
                         */

                        /* We have a command. */
                        c = (volatile CommandPtr) ((CMNamePtr) cm)->u.cmd;
                        if ((((CommandPtr) c)->maxargs != kNoMax) && ((((CmdLineInfoPtr) clp)->argCount - 1) > ((CommandPtr) c)->maxargs)) {
                              PrintCmdUsage((CommandPtr) c);
                              err = -1;
                        } else if ((((CommandPtr) c)->minargs != kNoMax) && ((((CmdLineInfoPtr) clp)->argCount - 1) < ((CommandPtr) c)->minargs)) {
                              PrintCmdUsage((CommandPtr) c);
                              err = -1;
                        } else if (((((CommandPtr) c)->flags & kCmdMustBeConnected) != 0) && (gConnected == 0)) {
                              Error(kDontPerror, "Not connected.\n");
                              err = -1;
                        } else if (((((CommandPtr) c)->flags & kCmdMustBeDisconnected) != 0) && (gConnected == 1)) {
                              Error(kDontPerror, "You must close the connection first before doing this command.\n");
                              err = -1;
                        } else {
                              if ((((CommandPtr) c)->flags & kCmdWaitMsg) != 0) {
                                    SetBar(NULL, "RUNNING", NULL, -1, 1);
                              }
                              /* Run our command finally. */
                              if (setjmp(gCommandJmp)) {
                                    /* Command was interrupted. */
                                    (void) SIGNAL(SIGINT, SIG_IGN);
                                    (void) SIGNAL(SIGPIPE, SIG_IGN);
                              } else {
                                    si = (volatile Sig_t) SIGNAL(SIGINT, SIG_IGN);
                                    sp = (volatile Sig_t) SIGNAL(SIGPIPE, SigPipeExecCmd);
                        
                                    /* Make a copy of the real stdout stream, so we can restore
                                     * it after the command finishes.
                                     */
                                    ((CmdLineInfoPtr) clp)->savedStdout = gStdout;
                                    ((CmdLineInfoPtr) clp)->outFile = -1;
                                    
                                    /* Don't > or | if we this command is the shell
                                     * command (!), or any other command that specifies
                                     * the kCmdNoRedirect flag.
                                     */
                                    if (!(((CommandPtr) c)->flags & kCmdNoRedirect)) {
                                          /* Open the output file if the user supplied one.
                                           * This file can be something being redirected into,
                                           * such as ">outfile" or a file to pipe output into,
                                           * such as "| wc."
                                           */
                                          if (*((CmdLineInfoPtr) clp)->outFileName) {
                                                if (!((CmdLineInfoPtr) clp)->isAppend)
                                                      ((CmdLineInfoPtr) clp)->outFile = open(((CmdLineInfoPtr) clp)->outFileName,
                                                            O_WRONLY | O_TRUNC | O_CREAT,
                                                            S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
                                                else
                                                      ((CmdLineInfoPtr) clp)->outFile = open(((CmdLineInfoPtr) clp)->outFileName,
                                                            O_WRONLY | O_APPEND | O_CREAT);

                                                if (((CmdLineInfoPtr) clp)->outFile == -1) {
                                                      Error(kDoPerror, "Could not open %s for writing.\n", ((CmdLineInfoPtr) clp)->outFileName);
                                                      err = -1;
                                                      goto done;
                                                }
                                          } else if ((*((CmdLineInfoPtr) clp)->pipeCmdLine) && !(((CommandPtr) c)->flags & kCmdDelayPipe)) {

                                                DebugMsg("|: '%s'\n", ((CmdLineInfoPtr) clp)->pipeCmdLine);
                                                pipefp = (FILE *) POpen(((CmdLineInfoPtr) clp)->pipeCmdLine, "w", 0);
                                                if (pipefp == NULL) {
                                                      Error(kDoPerror, "Could not pipe out to: %s\n", ((CmdLineInfoPtr) clp)->pipeCmdLine);
                                                      err = -1;
                                                      goto done;
                                                }
                                                ((CmdLineInfoPtr) clp)->outFile = fileno((FILE *) pipefp);
                                          }
                                    }
                                    if (((CmdLineInfoPtr) clp)->outFile != -1) {
                                          /* Replace stdout with the FILE pointer we want to
                                           * write to, so things like PrintF will print to
                                           * the file instead of the screen.
                                           */
                                          gStdout = ((CmdLineInfoPtr) clp)->outFile;
                                    }
                                    (void) SIGNAL(SIGINT, SigIntExecCmd);
                                    err = (*((CommandPtr) c)->proc) (((CmdLineInfoPtr) clp)->argCount, ((CmdLineInfoPtr) clp)->argVector);
                                    (void) SIGNAL(SIGINT, SIG_IGN);
                                    (void) SIGNAL(SIGPIPE, SIG_IGN);
                                    if (err == kUsageErr)
                                          PrintCmdUsage((CommandPtr) c);
                              }
            
                              /* We will clean up the mess now.  The command itself may
                               * have opened a pipe and done the initial setup, but
                               * it still depends on us to close everything.
                               */
                              if (((CmdLineInfoPtr) clp)->outFile != -1) {
                                    /* We've run the command, and now it's time to cleanup
                                     * our mess.  We close the output file we were writing
                                     * to, and replace the stdout variable with the real
                                     * stdout stream.
                                     */
                                    gStdout = ((CmdLineInfoPtr) clp)->savedStdout;
                                    if (*((CmdLineInfoPtr) clp)->outFileName)
                                          (void) close(((CmdLineInfoPtr) clp)->outFile);
                                    else if (pipefp != NULL)
                                          (void) pclose((FILE *) pipefp);
                              }
                              /* End if we tried to run a command. */
                        }
                        /* End if we had a command. */
                  }
                  /* End if we had a macro or command to try. */
            }
            /* End if we had atleast one argument. */
      }
done:
      --depth;
      if (clp != (volatile CmdLineInfoPtr) 0)
            free(clp);
      if (si != (VSig_t) kNoSignalHandler)
            (void) SIGNAL(SIGINT, si);
      if (sp != (VSig_t) kNoSignalHandler)
            (void) SIGNAL(SIGPIPE, sp);
      return err;
}                                                        /* ExecCommandLine */




/* Look for commands in the FILE pointer supplied, running each
 * one in turn.
 */
void RunScript(FILE *fp)
{
      char line[256];

      while (!gDoneApplication) {
            if (FGets(line, sizeof(line), fp) == NULL)
                  break;      /* Done. */
            ExecCommandLine(line);
      }
}     /* RunScript */




void RunStartupScript(void)
{
      FILE *fp;
      longstring path;
      
      (void) OurDirectoryPath(path, sizeof(path), kStartupScript);
      fp = fopen(path, "r");
      if (fp != NULL) {
            RunScript(fp);
            fclose(fp);
      }
}     /* RunStartupScript */




void CommandShell(void)
{
      char line[256];
      time_t cmdStartTime, cmdStopTime;

      /* We now set gEventNumber to 1, meaning that we have entered the
       * interactive command shell.  Some commands check gEventNumber
       * against 0, meaning that we hadn't entered the shell yet.
       */
      gEventNumber = 1L;

      if (setjmp(gCmdLoopJmp)) {
            /* Interrupted. */
            ++gNumInterruptions;
            if (gNumInterruptions == 3)
                  EPrintF("(Interrupt the program again to kill program.)\n");
            else if (gNumInterruptions > 3)
                  gDoneApplication = 1;
            DebugMsg("\nReturning to top level.\n");
      }

      while (!gDoneApplication) {
            (void) SIGNAL(SIGPIPE, SIG_IGN);
            (void) SIGNAL(SIGINT, SigIntCmdLoop);

            /* If the user logged out and left us in the background,
             * quit, unless a script is running.
             */
            if (!gDoingScript && !UserLoggedIn())
                  break;            /* User hung up. */
            (void) CheckNewMail();
            if (Gets(line, sizeof(line)) == NULL)
                  break;      /* Done. */
            if (gWinInit) {
                  if (gBlankLines)
                        PrintF("\n");
                  BoldPrintF("> %s\n", line);
                  if (gBlankLines)
                        PrintF("\n");
            }
            FlushListWindow();

            time(&cmdStartTime);
            ExecCommandLine(line);
            time(&cmdStopTime);
            if ((int) (cmdStopTime - cmdStartTime) > kBeepAfterCmdTime)
                  Beep(1);

            ++gEventNumber;
      }
}     /* CommandShell */

Generated by  Doxygen 1.6.0   Back to index