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 >
Wrap
C/C++ Source or Header
|
1995-11-26
|
16KB
|
581 lines
/* 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 sigNum)
{
DebugMsg("\n*Broken Pipe*\n");
SIGNAL(SIGPIPE, SigPipeExecCmd);
/* alarm(0); */
/* longjmp(gCommandJmp, 1); */
} /* SigPipeExecCmd */
/*ARGSUSED*/
static void
SigIntExecCmd(int sigNum)
{
EPrintF("\n*Command Interrupted*\n");
SIGNAL(SIGINT, SigIntExecCmd);
alarm(0);
longjmp(gCommandJmp, 1);
} /* SigIntExecCmd */
/*ARGSUSED*/
static void
SigIntCmdLoop(int sigNum)
{
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;
volatile FILE *pipefp;
volatile CmdLineInfoPtr clp;
VSig_t si, sp;
static int depth = 0;
string str;
sp = (VSig_t) kNoSignalHandler;
si = (VSig_t) kNoSignalHandler;
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
(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 */