reactos/rosapps/net/ncftp/ncftp/shell.c
Steven Edwards 86bda6b3d9 Added ncftp to rosapps
svn path=/trunk/; revision=2453
2001-12-30 06:31:25 +00:00

607 lines
13 KiB
C

/* shell.c
*
* Copyright (c) 1992-2001 by Mike Gleason.
* All rights reserved.
*
*/
#include "syshdrs.h"
#include "shell.h"
#include "util.h"
#include "bookmark.h"
#include "cmds.h"
#include "readln.h"
#include "trace.h"
#include "main.h"
/* 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. */
int gEventNumber = 0;
#if defined(WIN32) || defined(_WINDOWS)
#elif defined(HAVE_SIGSETJMP)
/* A command function can set this to have a user generated signal
* cause execution to jump here.
*/
sigjmp_buf gCancelJmp;
/* This is used by the shell so that an unexpected signal can have
* execution come back to the main shell prompt.
*/
sigjmp_buf gBackToTopJmp;
#else /* HAVE_SIGSETJMP */
/* A command function can set this to have a user generated signal
* cause execution to jump here.
*/
jmp_buf gCancelJmp;
/* This is used by the shell so that an unexpected signal can have
* execution come back to the main shell prompt.
*/
jmp_buf gBackToTopJmp;
#endif /* HAVE_SIGSETJMP */
/* Flag specifying whether the jmp has been set. */
int gMayCancelJmp = 0;
/* Flag specifying whether the jmp has been set. */
int gMayBackToTopJmp = 0;
/* Save the last signal number. */
int gGotSig = 0;
/* If the shell is running one of our commands, this is set to non-zero. */
int gRunningCommand = 0;
/* If set, we need to abort the current session. */
int gCancelCtrl = 0;
extern Command gCommands[];
extern size_t gNumCommands;
extern int gStartupUrlParameterGiven;
extern FTPLibraryInfo gLib;
extern FTPConnectionInfo gConn;
extern LineList gStartupURLCdList;
extern int gNumProgramRuns;
extern char gCopyright[];
/* This is used as the comparison function when we sort the name list. */
static int
CommandSortCmp(const CommandPtr a, const CommandPtr b)
{
return (strcmp((*a).name, (*b).name));
} /* CommandSortCmp */
/* Sort the command list, in case it wasn't hard-coded that way. */
void
InitCommandList(void)
{
qsort(gCommands, gNumCommands, sizeof(Command), (qsort_proc_t) CommandSortCmp);
} /* InitCommandList */
/* This is used as the comparison function when we lookup something
* in the command list, and when we want an exact match.
*/
static int
CommandExactSearchCmp(const char *const key, const CommandPtr b)
{
return (strcmp(key, (*b).name));
} /* CommandExactSearchCmp */
/* This is used as the comparison function when we lookup something
* in the command 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
CommandSubSearchCmp(const char *const key, const CommandPtr a)
{
register const char *kcp, *cp;
int d;
for (cp = (*a).name, kcp = key; ; ) {
if (*kcp == 0)
break;
d = *kcp++ - *cp++;
if (d)
return d;
}
return (0);
} /* CommandSubSearchCmp */
/* This returns a pointer to a Command, if the name supplied was long
* enough to be a unique name. We return a 0 CommandPtr if we did not
* find any matches, a -1 CommandPtr if we found more than one match,
* or the unique CommandPtr.
*/
CommandPtr
GetCommandByIndex(const int i)
{
if ((i < 0) || (i >= (int) gNumCommands))
return (kNoCommand);
return (&gCommands[i]);
} /* GetCommandByIndex */
/* This returns a pointer to a Command, if the name supplied was long
* enough to be a unique name. We return a 0 CommandPtr if we did not
* find any matches, a -1 CommandPtr if we found more than one match,
* or the unique CommandPtr.
*/
CommandPtr
GetCommandByName(const char *const name, int wantExactMatch)
{
CommandPtr 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 = (CommandPtr) bsearch(name, gCommands, gNumCommands, sizeof(Command), (bsearch_proc_t) CommandExactSearchCmp);
if (canp == kNoCommand && !wantExactMatch) {
/* Now see if the user typed an abbreviation unique enough
* to match only one name in the list.
*/
canp = (CommandPtr) bsearch(name, gCommands, gNumCommands, sizeof(Command), (bsearch_proc_t) CommandSubSearchCmp);
if (canp != kNoCommand) {
/* Check the entry above us and see if the name we're looking
* for would match that, too.
*/
if (canp != &gCommands[0]) {
canp2 = canp - 1;
if (CommandSubSearchCmp(name, canp2) == 0)
return kAmbiguousCommand;
}
/* Check the entry below us and see if the name we're looking
* for would match that one.
*/
if (canp != &gCommands[gNumCommands - 1]) {
canp2 = canp + 1;
if (CommandSubSearchCmp(name, canp2) == 0)
return kAmbiguousCommand;
}
}
}
return canp;
} /* GetCommandByName */
/* Print the help string for the command specified. */
void
PrintCmdHelp(CommandPtr c)
{
(void) 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)
(void) printf("Usage: %s %s\n", c->name, c->usage);
} /* PrintCmdUsage */
/* Parse a command line into an array of arguments. */
int
MakeArgv(char *line, int *cargc, const char **cargv, int cargcmax, char *dbuf, size_t dbufsize, int *noglobargv, int readlineHacks)
{
int c;
int retval;
char *dlim;
char *dcp;
char *scp;
char *arg;
*cargc = 0;
scp = line;
dlim = dbuf + dbufsize - 1;
dcp = dbuf;
for (*cargc = 0; *cargc < cargcmax; ) {
/* Eat preceding junk. */
for ( ; ; scp++) {
c = *scp;
if (c == '\0')
goto done;
if (isspace(c))
continue;
if ((c == ';') || (c == '\n')) {
scp++;
goto done;
}
break;
}
arg = dcp;
cargv[*cargc] = arg;
noglobargv[*cargc] = 0;
(*cargc)++;
/* Special hack so that "!cmd" is always split into "!" "cmd" */
if ((*cargc == 1) && (*scp == '!')) {
if (scp[1] == '!') {
scp[1] = '\0';
} else if ((scp[1] != '\0') && (!isspace((int) scp[1]))) {
cargv[0] = "!";
scp++;
arg = dcp;
cargv[*cargc] = arg;
noglobargv[*cargc] = 0;
(*cargc)++;
}
}
/* Add characters to the new argument. */
for ( ; ; ) {
c = *scp;
if (c == '\0')
break;
if (isspace(c))
break;
if ((c == ';') || (c == '\n')) {
break;
}
scp++;
if (c == '\'') {
for ( ; ; ) {
c = *scp++;
if (c == '\0') {
if (readlineHacks != 0)
break;
/* Syntax error */
(void) fprintf(stderr, "Error: Unbalanced quotes.\n");
return (-1);
}
if (c == '\'')
break;
/* Add char. */
if (dcp >= dlim)
goto toolong;
*dcp++ = c;
if (strchr(kGlobChars, c) != NULL) {
/* User quoted glob characters,
* so mark this argument for
* noglob.
*/
noglobargv[*cargc - 1] = 1;
}
}
} else if (c == '"') {
for ( ; ; ) {
c = *scp++;
if (c == '\0') {
if (readlineHacks != 0)
break;
/* Syntax error */
(void) fprintf(stderr, "Error: Unbalanced quotes.\n");
return (-1);
}
if (c == '"')
break;
/* Add char. */
if (dcp >= dlim)
goto toolong;
*dcp++ = c;
if (strchr(kGlobChars, c) != NULL) {
/* User quoted glob characters,
* so mark this argument for
* noglob.
*/
noglobargv[*cargc - 1] = 1;
}
}
} else
#if defined(WIN32) || defined(_WINDOWS)
if (c == '|') {
#else
if (c == '\\') {
#endif
/* Add next character, verbatim. */
c = *scp++;
if (c == '\0')
break;
/* Add char. */
if (dcp >= dlim)
goto toolong;
*dcp++ = c;
} else {
/* Add char. */
if (dcp >= dlim)
goto toolong;
*dcp++ = c;
}
}
*dcp++ = '\0';
}
(void) fprintf(stderr, "Error: Argument list too long.\n");
*cargc = 0;
cargv[*cargc] = NULL;
return (-1);
done:
retval = (int) (scp - line);
cargv[*cargc] = NULL;
return (retval);
toolong:
(void) fprintf(stderr, "Error: Line too long.\n");
*cargc = 0;
cargv[*cargc] = NULL;
return (-1);
} /* MakeArgv */
static int
DoCommand(const ArgvInfoPtr aip)
{
CommandPtr cmdp;
int flags;
int cargc, cargcm1;
cmdp = GetCommandByName(aip->cargv[0], 0);
if (cmdp == kAmbiguousCommand) {
(void) printf("%s: ambiguous command name.\n", aip->cargv[0]);
return (-1);
} else if (cmdp == kNoCommand) {
(void) printf("%s: no such command.\n", aip->cargv[0]);
return (-1);
}
cargc = aip->cargc;
cargcm1 = cargc - 1;
flags = cmdp->flags;
if (((flags & kCmdMustBeConnected) != 0) && (gConn.connected == 0)) {
(void) printf("%s: must be connected to do that.\n", aip->cargv[0]);
} else if (((flags & kCmdMustBeDisconnected) != 0) && (gConn.connected != 0)) {
(void) printf("%s: must be disconnected to do that.\n", aip->cargv[0]);
} else if ((cmdp->minargs != kNoMin) && (cmdp->minargs > cargcm1)) {
PrintCmdUsage(cmdp);
} else if ((cmdp->maxargs != kNoMax) && (cmdp->maxargs < cargcm1)) {
PrintCmdUsage(cmdp);
} else {
(*cmdp->proc)(cargc, aip->cargv, cmdp, aip);
}
return (0);
} /* DoCommand */
/* Allows the user to cancel a data transfer. */
void
XferCanceller(int sigNum)
{
gGotSig = sigNum;
if (gConn.cancelXfer > 0) {
#if defined(WIN32) || defined(_WINDOWS)
signal(SIGINT, SIG_DFL);
#else
/* User already tried it once, they
* must think it's locked up.
*
* Jump back to the top, and
* close down the current session.
*/
gCancelCtrl = 1;
if (gMayBackToTopJmp > 0) {
#ifdef HAVE_SIGSETJMP
siglongjmp(gBackToTopJmp, 1);
#else /* HAVE_SIGSETJMP */
longjmp(gBackToTopJmp, 1);
#endif /* HAVE_SIGSETJMP */
}
#endif
}
gConn.cancelXfer++;
} /* XferCanceller */
#if defined(WIN32) || defined(_WINDOWS)
#else
/* Allows the user to cancel a long operation and get back to the shell. */
void
BackToTop(int sigNum)
{
gGotSig = sigNum;
if (sigNum == SIGPIPE) {
if (gRunningCommand == 1) {
(void) fprintf(stderr, "Unexpected broken pipe.\n");
gRunningCommand = 0;
} else {
SetXtermTitle("RESTORE");
exit(1);
}
} else if (sigNum == SIGINT) {
if (gRunningCommand == 0)
gDoneApplication = 1;
}
if (gMayBackToTopJmp > 0) {
#ifdef HAVE_SIGSETJMP
siglongjmp(gBackToTopJmp, 1);
#else /* HAVE_SIGSETJMP */
longjmp(gBackToTopJmp, 1);
#endif /* HAVE_SIGSETJMP */
}
} /* BackToTop */
/* Some commands may want to jump back to the start too. */
void
Cancel(int sigNum)
{
if (gMayCancelJmp != 0) {
gGotSig = sigNum;
gMayCancelJmp = 0;
#ifdef HAVE_SIGSETJMP
siglongjmp(gCancelJmp, 1);
#else /* HAVE_SIGSETJMP */
longjmp(gCancelJmp, 1);
#endif /* HAVE_SIGSETJMP */
}
} /* Cancel */
#endif
void
CommandShell(void)
{
int tUsed, bUsed;
ArgvInfo ai;
char prompt[64];
char *lineRead;
#if defined(WIN32) || defined(_WINDOWS)
#else
int sj;
#endif
time_t cmdStart, cmdStop;
/* Execution may jump back to this point to restart the shell. */
#if defined(WIN32) || defined(_WINDOWS)
#elif defined(HAVE_SIGSETJMP)
sj = sigsetjmp(gBackToTopJmp, 1);
#else /* HAVE_SIGSETJMP */
sj = setjmp(gBackToTopJmp);
#endif /* HAVE_SIGSETJMP */
#if defined(WIN32) || defined(_WINDOWS)
#else
if (sj != 0) {
Trace(0, "Caught signal %d, back at top.\n", gGotSig);
if (gGotSig == SIGALRM) {
(void) printf("\nRemote host was not responding, closing down the session.");
FTPShutdownHost(&gConn);
} else{
(void) printf("\nInterrupted.\n");
if (gCancelCtrl != 0) {
gCancelCtrl = 0;
(void) printf("Closing down the current FTP session: ");
FTPShutdownHost(&gConn);
(void) sleep(1);
(void) printf("done.\n");
}
}
}
gMayBackToTopJmp = 1;
#endif
++gEventNumber;
while (gDoneApplication == 0) {
#if defined(WIN32) || defined(_WINDOWS)
#else
(void) NcSignal(SIGINT, BackToTop);
(void) NcSignal(SIGPIPE, BackToTop);
(void) NcSignal(SIGALRM, BackToTop);
#endif
MakePrompt(prompt, sizeof(prompt));
if (gConn.connected == 0) {
SetXtermTitle("DEFAULT");
} else {
SetXtermTitle("%s - NcFTP", gConn.host);
}
lineRead = Readline(prompt);
if (lineRead == NULL) {
/* EOF, Control-D */
(void) printf("\n");
break;
}
Trace(0, "> %s\n", lineRead);
AddHistory(lineRead);
for (tUsed = 0;;) {
(void) memset(&ai, 0, sizeof(ai));
bUsed = MakeArgv(lineRead + tUsed, &ai.cargc, ai.cargv,
(int) (sizeof(ai.cargv) / sizeof(char *)),
ai.argbuf, sizeof(ai.argbuf),
ai.noglobargv, 0);
if (bUsed <= 0)
break;
tUsed += bUsed;
if (ai.cargc == 0)
continue;
gRunningCommand = 1;
(void) time(&cmdStart);
if (DoCommand(&ai) < 0) {
(void) time(&cmdStop);
gRunningCommand = 0;
break;
}
(void) time(&cmdStop);
gRunningCommand = 0;
if ((cmdStop - cmdStart) > kBeepAfterCmdTime) {
/* Let the user know that a time-consuming
* operation has completed.
*/
#if defined(WIN32) || defined(_WINDOWS)
MessageBeep(MB_OK);
#else
(void) fprintf(stderr, "\007");
#endif
}
++gEventNumber;
}
free(lineRead);
}
CloseHost();
gMayBackToTopJmp = 0;
} /* Shell */