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