1 /* shell.c
2  *
3  * Copyright (c) 1992-2001 by Mike Gleason.
4  * All rights reserved.
5  *
6  */
7 
8 #include "syshdrs.h"
9 
10 #include "shell.h"
11 #include "util.h"
12 #include "bookmark.h"
13 #include "cmds.h"
14 #include "readln.h"
15 #include "trace.h"
16 #include "main.h"
17 
18 /* We keep running the command line interpreter until gDoneApplication
19  * is non-zero.
20  */
21 int gDoneApplication = 0;
22 
23 /* Track how many times they use ^C. */
24 int gNumInterruptions = 0;
25 
26 /* Keep a count of the number of commands the user has entered. */
27 int gEventNumber = 0;
28 
29 
30 #if defined(WIN32) || defined(_WINDOWS)
31 #elif defined(HAVE_SIGSETJMP)
32 /* A command function can set this to have a user generated signal
33  * cause execution to jump here.
34  */
35 sigjmp_buf gCancelJmp;
36 
37 /* This is used by the shell so that an unexpected signal can have
38  * execution come back to the main shell prompt.
39  */
40 sigjmp_buf gBackToTopJmp;
41 #else	/* HAVE_SIGSETJMP */
42 /* A command function can set this to have a user generated signal
43  * cause execution to jump here.
44  */
45 jmp_buf gCancelJmp;
46 
47 /* This is used by the shell so that an unexpected signal can have
48  * execution come back to the main shell prompt.
49  */
50 jmp_buf gBackToTopJmp;
51 #endif	/* HAVE_SIGSETJMP */
52 
53 /* Flag specifying whether the jmp has been set. */
54 int gMayCancelJmp = 0;
55 
56 /* Flag specifying whether the jmp has been set. */
57 int gMayBackToTopJmp = 0;
58 
59 /* Save the last signal number. */
60 int gGotSig = 0;
61 
62 /* If the shell is running one of our commands, this is set to non-zero. */
63 int gRunningCommand = 0;
64 
65 /* If set, we need to abort the current session. */
66 int gCancelCtrl = 0;
67 
68 extern Command gCommands[];
69 extern size_t gNumCommands;
70 extern int gStartupUrlParameterGiven;
71 extern FTPLibraryInfo gLib;
72 extern FTPConnectionInfo gConn;
73 extern LineList gStartupURLCdList;
74 extern int gNumProgramRuns;
75 extern char gCopyright[];
76 
77 
78 /* This is used as the comparison function when we sort the name list. */
79 static int
CommandSortCmp(const CommandPtr a,const CommandPtr b)80 CommandSortCmp(const CommandPtr a, const CommandPtr b)
81 {
82 	return (strcmp((*a).name, (*b).name));
83 }	/* CommandSortCmp */
84 
85 
86 
87 
88 /* Sort the command list, in case it wasn't hard-coded that way. */
89 void
InitCommandList(void)90 InitCommandList(void)
91 {
92 	qsort(gCommands, gNumCommands, sizeof(Command), (qsort_proc_t) CommandSortCmp);
93 }	/* InitCommandList */
94 
95 
96 
97 
98 /* This is used as the comparison function when we lookup something
99  * in the command list, and when we want an exact match.
100  */
101 static int
CommandExactSearchCmp(const char * const key,const CommandPtr b)102 CommandExactSearchCmp(const char *const key, const CommandPtr b)
103 {
104 	return (strcmp(key, (*b).name));
105 }	/* CommandExactSearchCmp */
106 
107 
108 
109 
110 /* This is used as the comparison function when we lookup something
111  * in the command list, and when the key can be just the first few
112  * letters of one or more commands.  So a key of "qu" might would match
113  * "quit" and "quote" for example.
114  */
115 static int
CommandSubSearchCmp(const char * const key,const CommandPtr a)116 CommandSubSearchCmp(const char *const key, const CommandPtr a)
117 {
118 	register const char *kcp, *cp;
119 	int d;
120 
121 	for (cp = (*a).name, kcp = key; ; ) {
122 		if (*kcp == 0)
123 			break;
124 		d = *kcp++ - *cp++;
125 		if (d)
126 			return d;
127 	}
128 	return (0);
129 }	/* CommandSubSearchCmp */
130 
131 
132 
133 
134 /* This returns a pointer to a Command, if the name supplied was long
135  * enough to be a unique name.  We return a 0 CommandPtr if we did not
136  * find any matches, a -1 CommandPtr if we found more than one match,
137  * or the unique CommandPtr.
138  */
139 CommandPtr
GetCommandByIndex(const int i)140 GetCommandByIndex(const int i)
141 {
142 	if ((i < 0) || (i >= (int) gNumCommands))
143 		return (kNoCommand);
144 	return (&gCommands[i]);
145 }									   /* GetCommandByIndex */
146 
147 
148 
149 
150 /* This returns a pointer to a Command, if the name supplied was long
151  * enough to be a unique name.  We return a 0 CommandPtr if we did not
152  * find any matches, a -1 CommandPtr if we found more than one match,
153  * or the unique CommandPtr.
154  */
155 CommandPtr
GetCommandByName(const char * const name,int wantExactMatch)156 GetCommandByName(const char *const name, int wantExactMatch)
157 {
158 	CommandPtr canp, canp2;
159 
160 	/* First check for an exact match.  Otherwise if you if asked for
161 	 * 'cd', it would match both 'cd' and 'cdup' and return an
162 	 * ambiguous name error, despite having the exact name for 'cd.'
163 	 */
164 	canp = (CommandPtr) bsearch(name, gCommands, gNumCommands, sizeof(Command), (bsearch_proc_t) CommandExactSearchCmp);
165 
166 	if (canp == kNoCommand && !wantExactMatch) {
167 		/* Now see if the user typed an abbreviation unique enough
168 		 * to match only one name in the list.
169 		 */
170 		canp = (CommandPtr) bsearch(name, gCommands, gNumCommands, sizeof(Command), (bsearch_proc_t) CommandSubSearchCmp);
171 
172 		if (canp != kNoCommand) {
173 			/* Check the entry above us and see if the name we're looking
174 			 * for would match that, too.
175 			 */
176 			if (canp != &gCommands[0]) {
177 				canp2 = canp - 1;
178 				if (CommandSubSearchCmp(name, canp2) == 0)
179 					return kAmbiguousCommand;
180 			}
181 			/* Check the entry below us and see if the name we're looking
182 			 * for would match that one.
183 			 */
184 			if (canp != &gCommands[gNumCommands - 1]) {
185 				canp2 = canp + 1;
186 				if (CommandSubSearchCmp(name, canp2) == 0)
187 					return kAmbiguousCommand;
188 			}
189 		}
190 	}
191 	return canp;
192 }									   /* GetCommandByName */
193 
194 
195 
196 
197 /* Print the help string for the command specified. */
198 
199 void
PrintCmdHelp(CommandPtr c)200 PrintCmdHelp(CommandPtr c)
201 {
202 	(void) printf("%s: %s.\n", c->name, c->help);
203 }									   /* PrintCmdHelp */
204 
205 
206 
207 
208 /* Print the usage string for the command specified. */
209 void
PrintCmdUsage(CommandPtr c)210 PrintCmdUsage(CommandPtr c)
211 {
212 	if (c->usage != NULL)
213 		(void) printf("Usage: %s %s\n", c->name, c->usage);
214 }									   /* PrintCmdUsage */
215 
216 
217 
218 
219 /* Parse a command line into an array of arguments. */
220 int
MakeArgv(char * line,int * cargc,const char ** cargv,int cargcmax,char * dbuf,size_t dbufsize,int * noglobargv,int readlineHacks)221 MakeArgv(char *line, int *cargc, const char **cargv, int cargcmax, char *dbuf, size_t dbufsize, int *noglobargv, int readlineHacks)
222 {
223 	int c;
224 	int retval;
225 	char *dlim;
226 	char *dcp;
227 	char *scp;
228 	char *arg;
229 
230 	*cargc = 0;
231 	scp = line;
232 	dlim = dbuf + dbufsize - 1;
233 	dcp = dbuf;
234 
235 	for (*cargc = 0; *cargc < cargcmax; ) {
236 		/* Eat preceding junk. */
237 		for ( ; ; scp++) {
238 			c = *scp;
239 			if (c == '\0')
240 				goto done;
241 			if (isspace(c))
242 				continue;
243 			if ((c == ';') || (c == '\n')) {
244 				scp++;
245 				goto done;
246 			}
247 			break;
248 		}
249 
250 		arg = dcp;
251 		cargv[*cargc] = arg;
252 		noglobargv[*cargc] = 0;
253 		(*cargc)++;
254 
255 		/* Special hack so that "!cmd" is always split into "!" "cmd" */
256 		if ((*cargc == 1) && (*scp == '!')) {
257 			if (scp[1] == '!') {
258 				scp[1] = '\0';
259 			} else if ((scp[1] != '\0') && (!isspace((int) scp[1]))) {
260 				cargv[0] = "!";
261 				scp++;
262 				arg = dcp;
263 				cargv[*cargc] = arg;
264 				noglobargv[*cargc] = 0;
265 				(*cargc)++;
266 			}
267 		}
268 
269 		/* Add characters to the new argument. */
270 		for ( ; ; ) {
271 			c = *scp;
272 			if (c == '\0')
273 				break;
274 			if (isspace(c))
275 				break;
276 			if ((c == ';') || (c == '\n')) {
277 				break;
278 			}
279 
280 			scp++;
281 
282 			if (c == '\'') {
283 				for ( ; ; ) {
284 					c = *scp++;
285 					if (c == '\0') {
286 						if (readlineHacks != 0)
287 							break;
288 						/* Syntax error */
289 						(void) fprintf(stderr, "Error: Unbalanced quotes.\n");
290 						return (-1);
291 					}
292 					if (c == '\'')
293 						break;
294 
295 					/* Add char. */
296 					if (dcp >= dlim)
297 						goto toolong;
298 					*dcp++ = c;
299 
300 					if (strchr(kGlobChars, c) != NULL) {
301 						/* User quoted glob characters,
302 						 * so mark this argument for
303 						 * noglob.
304 						 */
305 						noglobargv[*cargc - 1] = 1;
306 					}
307 				}
308 			} else if (c == '"') {
309 				for ( ; ; ) {
310 					c = *scp++;
311 					if (c == '\0') {
312 						if (readlineHacks != 0)
313 							break;
314 						/* Syntax error */
315 						(void) fprintf(stderr, "Error: Unbalanced quotes.\n");
316 						return (-1);
317 					}
318 					if (c == '"')
319 						break;
320 
321 					/* Add char. */
322 					if (dcp >= dlim)
323 						goto toolong;
324 					*dcp++ = c;
325 
326 					if (strchr(kGlobChars, c) != NULL) {
327 						/* User quoted glob characters,
328 						 * so mark this argument for
329 						 * noglob.
330 						 */
331 						noglobargv[*cargc - 1] = 1;
332 					}
333 				}
334 			} else
335 #if defined(WIN32) || defined(_WINDOWS)
336 				if (c == '|') {
337 #else
338 				if (c == '\\') {
339 #endif
340 				/* Add next character, verbatim. */
341 				c = *scp++;
342 				if (c == '\0')
343 					break;
344 
345 				/* Add char. */
346 				if (dcp >= dlim)
347 					goto toolong;
348 				*dcp++ = c;
349 			} else {
350 				/* Add char. */
351 				if (dcp >= dlim)
352 					goto toolong;
353 				*dcp++ = c;
354 			}
355 		}
356 
357 		*dcp++ = '\0';
358 	}
359 
360 	(void) fprintf(stderr, "Error: Argument list too long.\n");
361 	*cargc = 0;
362 	cargv[*cargc] = NULL;
363 	return (-1);
364 
365 done:
366 	retval = (int) (scp - line);
367 	cargv[*cargc] = NULL;
368 	return (retval);
369 
370 toolong:
371 	(void) fprintf(stderr, "Error: Line too long.\n");
372 	*cargc = 0;
373 	cargv[*cargc] = NULL;
374 	return (-1);
375 }	/* MakeArgv */
376 
377 
378 
379 
380 static int
381 DoCommand(const ArgvInfoPtr aip)
382 {
383 	CommandPtr cmdp;
384 	int flags;
385 	int cargc, cargcm1;
386 
387 	cmdp = GetCommandByName(aip->cargv[0], 0);
388 	if (cmdp == kAmbiguousCommand) {
389 		(void) printf("%s: ambiguous command name.\n", aip->cargv[0]);
390 		return (-1);
391 	} else if (cmdp == kNoCommand) {
392 		(void) printf("%s: no such command.\n", aip->cargv[0]);
393 		return (-1);
394 	}
395 
396 	cargc = aip->cargc;
397 	cargcm1 = cargc - 1;
398 	flags = cmdp->flags;
399 
400 	if (((flags & kCmdMustBeConnected) != 0) && (gConn.connected == 0)) {
401 		(void) printf("%s: must be connected to do that.\n", aip->cargv[0]);
402 	} else if (((flags & kCmdMustBeDisconnected) != 0) && (gConn.connected != 0)) {
403 		(void) printf("%s: must be disconnected to do that.\n", aip->cargv[0]);
404 	} else if ((cmdp->minargs != kNoMin) && (cmdp->minargs > cargcm1)) {
405 		PrintCmdUsage(cmdp);
406 	} else if ((cmdp->maxargs != kNoMax) && (cmdp->maxargs < cargcm1)) {
407 		PrintCmdUsage(cmdp);
408 	} else {
409 		(*cmdp->proc)(cargc, aip->cargv, cmdp, aip);
410 	}
411 	return (0);
412 }	/* DoCommand */
413 
414 
415 
416 
417 /* Allows the user to cancel a data transfer. */
418 void
419 XferCanceller(int sigNum)
420 {
421 	gGotSig = sigNum;
422 	if (gConn.cancelXfer > 0) {
423 #if defined(WIN32) || defined(_WINDOWS)
424 		signal(SIGINT, SIG_DFL);
425 #else
426 		/* User already tried it once, they
427 		 * must think it's locked up.
428 		 *
429 		 * Jump back to the top, and
430 		 * close down the current session.
431 		 */
432 		gCancelCtrl = 1;
433 		if (gMayBackToTopJmp > 0) {
434 #ifdef HAVE_SIGSETJMP
435 			siglongjmp(gBackToTopJmp, 1);
436 #else	/* HAVE_SIGSETJMP */
437 			longjmp(gBackToTopJmp, 1);
438 #endif	/* HAVE_SIGSETJMP */
439 		}
440 #endif
441 	}
442 	gConn.cancelXfer++;
443 }	/* XferCanceller */
444 
445 
446 
447 #if defined(WIN32) || defined(_WINDOWS)
448 #else
449 
450 /* Allows the user to cancel a long operation and get back to the shell. */
451 void
452 BackToTop(int sigNum)
453 {
454 	gGotSig = sigNum;
455 	if (sigNum == SIGPIPE) {
456 		if (gRunningCommand == 1) {
457 			(void) fprintf(stderr, "Unexpected broken pipe.\n");
458 			gRunningCommand = 0;
459 		} else {
460 			SetXtermTitle("RESTORE");
461 			exit(1);
462 		}
463 	} else if (sigNum == SIGINT) {
464 		if (gRunningCommand == 0)
465 			gDoneApplication = 1;
466 	}
467 	if (gMayBackToTopJmp > 0) {
468 #ifdef HAVE_SIGSETJMP
469 		siglongjmp(gBackToTopJmp, 1);
470 #else	/* HAVE_SIGSETJMP */
471 		longjmp(gBackToTopJmp, 1);
472 #endif	/* HAVE_SIGSETJMP */
473 	}
474 }	/* BackToTop */
475 
476 
477 
478 
479 /* Some commands may want to jump back to the start too. */
480 void
481 Cancel(int sigNum)
482 {
483 	if (gMayCancelJmp != 0) {
484 		gGotSig = sigNum;
485 		gMayCancelJmp = 0;
486 #ifdef HAVE_SIGSETJMP
487 		siglongjmp(gCancelJmp, 1);
488 #else	/* HAVE_SIGSETJMP */
489 		longjmp(gCancelJmp, 1);
490 #endif	/* HAVE_SIGSETJMP */
491 	}
492 }	/* Cancel */
493 
494 #endif
495 
496 
497 
498 void
499 CommandShell(void)
500 {
501 	int tUsed, bUsed;
502 	ArgvInfo ai;
503 	char prompt[64];
504 	char *lineRead;
505 #if defined(WIN32) || defined(_WINDOWS)
506 #else
507 	int sj;
508 #endif
509 	time_t cmdStart, cmdStop;
510 
511 	/* Execution may jump back to this point to restart the shell. */
512 #if defined(WIN32) || defined(_WINDOWS)
513 
514 #elif defined(HAVE_SIGSETJMP)
515 	sj = sigsetjmp(gBackToTopJmp, 1);
516 #else	/* HAVE_SIGSETJMP */
517 	sj = setjmp(gBackToTopJmp);
518 #endif	/* HAVE_SIGSETJMP */
519 
520 #if defined(WIN32) || defined(_WINDOWS)
521 #else
522 	if (sj != 0) {
523 		Trace(0, "Caught signal %d, back at top.\n", gGotSig);
524 		if (gGotSig == SIGALRM) {
525 			(void) printf("\nRemote host was not responding, closing down the session.");
526 			FTPShutdownHost(&gConn);
527 		} else{
528 			(void) printf("\nInterrupted.\n");
529 			if (gCancelCtrl != 0) {
530 				gCancelCtrl = 0;
531 				(void) printf("Closing down the current FTP session: ");
532 				FTPShutdownHost(&gConn);
533 				(void) sleep(1);
534 				(void) printf("done.\n");
535 			}
536 		}
537 	}
538 
539 	gMayBackToTopJmp = 1;
540 #endif
541 
542 
543 	++gEventNumber;
544 
545 	while (gDoneApplication == 0) {
546 #if defined(WIN32) || defined(_WINDOWS)
547 #else
548 		(void) NcSignal(SIGINT, BackToTop);
549 		(void) NcSignal(SIGPIPE, BackToTop);
550 		(void) NcSignal(SIGALRM, BackToTop);
551 #endif
552 
553 		MakePrompt(prompt, sizeof(prompt));
554 
555 		if (gConn.connected == 0) {
556 			SetXtermTitle("DEFAULT");
557 		} else {
558 			SetXtermTitle("%s - NcFTP", gConn.host);
559 		}
560 
561 		lineRead = Readline(prompt);
562 		if (lineRead == NULL) {
563 			/* EOF, Control-D */
564 			(void) printf("\n");
565 			break;
566 		}
567 		Trace(0, "> %s\n", lineRead);
568 		AddHistory(lineRead);
569 		for (tUsed = 0;;) {
570 			(void) memset(&ai, 0, sizeof(ai));
571 			bUsed = MakeArgv(lineRead + tUsed, &ai.cargc, ai.cargv,
572 				(int) (sizeof(ai.cargv) / sizeof(char *)),
573 				ai.argbuf, sizeof(ai.argbuf),
574 				ai.noglobargv, 0);
575 			if (bUsed <= 0)
576 				break;
577 			tUsed += bUsed;
578 			if (ai.cargc == 0)
579 				continue;
580 			gRunningCommand = 1;
581 			(void) time(&cmdStart);
582 			if (DoCommand(&ai) < 0) {
583 				(void) time(&cmdStop);
584 				gRunningCommand = 0;
585 				break;
586 			}
587 			(void) time(&cmdStop);
588 			gRunningCommand = 0;
589 			if ((cmdStop - cmdStart) > kBeepAfterCmdTime) {
590 				/* Let the user know that a time-consuming
591 				 * operation has completed.
592 				 */
593 #if defined(WIN32) || defined(_WINDOWS)
594 				MessageBeep(MB_OK);
595 #else
596 				(void) fprintf(stderr, "\007");
597 #endif
598 			}
599 			++gEventNumber;
600 		}
601 
602 		free(lineRead);
603 	}
604 
605 	CloseHost();
606 	gMayBackToTopJmp = 0;
607 }	/* Shell */
608