Hint added.
[AROS.git] / workbench / c / Shell / Shell.c
blob00b69e8011b757de9c563fa35779b0524d95767b
1 /*
2 Copyright © 1995-2014, The AROS Development Team. All rights reserved.
3 $Id$
4 */
6 /* TODO:
7 Break support (and +(0L) before execution) -- CreateNewProc()?
8 */
10 #define DEBUG 0
11 #include <dos/dos.h>
12 #include <dos/stdio.h>
13 #include <dos/cliinit.h>
14 #include <exec/libraries.h>
15 #include <exec/lists.h>
16 #include <libraries/expansionbase.h>
17 #include <proto/alib.h>
18 #include <proto/dos.h>
19 #include <proto/exec.h>
21 #include <ctype.h>
23 #include <aros/debug.h>
25 #include "Shell.h"
27 /* Prevent inclusion of the ErrorOutput() linklib
28 * routine from -lamiga, so that we don't need a global
29 * SysBase
31 #define ErrorOutput() (((struct Process *)FindTask(NULL))->pr_CES)
33 #define IS_SYSTEM ((ss->flags & (FNF_VALIDFLAGS | FNF_SYSTEM)) == (FNF_VALIDFLAGS | FNF_SYSTEM))
34 #define IS_SCRIPT (cli->cli_CurrentInput != cli->cli_StandardInput)
36 static LONG executeLine(ShellState *ss, STRPTR commandArgs);
38 BOOL setInteractive(struct CommandLineInterface *cli, ShellState *ss)
40 D(bug("Shell %ld: Flags = 0x%lx\n", ss->cliNumber, ss->flags));
41 D(bug("Shell %ld: cli_Interactive = %ld\n", ss->cliNumber,
42 cli->cli_Interactive));
43 D(bug("Shell %ld: cli_Background = %ld\n", ss->cliNumber,
44 cli->cli_Background));
45 D(bug("Shell %ld: cli_CurrentInput = %p\n", ss->cliNumber,
46 cli->cli_CurrentInput));
47 D(bug("Shell %ld: cli_StandardInput = %p\n", ss->cliNumber,
48 cli->cli_StandardInput));
49 if (!cli->cli_Background && IS_SCRIPT)
50 cli->cli_Background = DOSTRUE;
52 cli->cli_Interactive = (cli->cli_Background || IS_SCRIPT || IS_SYSTEM) ? DOSFALSE : DOSTRUE;
53 D(bug("Shell %ld: cli_Interactive => %ld\n", ss->cliNumber,
54 cli->cli_Interactive));
55 D(bug("Shell %ld: cli_Background => %ld\n", ss->cliNumber,
56 cli->cli_Background));
58 return cli->cli_Interactive;
61 /* First we execute the script, then we interact with the user */
62 LONG interact(ShellState *ss)
64 struct CommandLineInterface *cli = Cli();
65 Buffer in = {0}, out = {0};
66 BOOL moreLeft = FALSE;
67 LONG error = 0;
69 setInteractive(cli, ss);
71 /* pre-allocate input buffer */
72 if ((error = bufferAppend("?", 1, &in, SysBase))) /* FIXME drop when readLine ok */
73 return error;
75 do {
76 if ((error = Redirection_init(ss)) == 0)
78 cliPrompt(ss);
80 bufferReset(&in); /* reuse allocated buffers */
81 bufferReset(&out);
83 D(bug("Shell %ld: Reading in a line of input...\n",
84 ss->cliNumber));
85 error = readLine(ss, cli, &in, &moreLeft);
86 D(bug("Shell %ld: moreLeft=%d, error=%ld, Line is: "
87 "%ld bytes (%s)\n",
88 ss->cliNumber, moreLeft, error, in.len, in.buf));
90 if (error == 0 && in.len > 0)
91 error = checkLine(ss, &in, &out, TRUE);
93 /* The command may have modified cli_Background.
94 * C:Execute does that.
96 setInteractive(cli, ss);
98 /* As per AmigaMail Vol 2, "II-65: Writing a UserShell" */
99 if (IS_SYSTEM && !IS_SCRIPT)
100 moreLeft = FALSE;
102 if (!cli->cli_Interactive)
104 if (cli->cli_ReturnCode >= cli->cli_FailLevel) {
105 moreLeft = FALSE;
106 D(bug("Shell: cli_ReturnCode (%ld) >= cli->cli_FailLevel (%ld)\n", cli->cli_ReturnCode, cli->cli_FailLevel));
109 if (CheckSignal(SIGBREAKF_CTRL_D))
111 PrintFault(ERROR_BREAK, "Shell");
112 moreLeft = FALSE;
116 Redirection_release(ss);
119 if (moreLeft)
120 continue;
122 popInterpreterState(ss);
124 if (cli->cli_Interactive)
126 Printf("Process %ld ending\n", ss->cliNumber);
127 Flush(Output());
128 break;
131 if (IS_SCRIPT) {
132 D(bug("Shell %ld: Closing CLI input 0x%08lx\n", ss->cliNumber,
133 cli->cli_CurrentInput));
134 Close(cli->cli_CurrentInput);
136 /* Now that we've closed CurrentInput, we can delete
137 * the CommandFile.
139 if (AROS_BSTR_strlen(cli->cli_CommandFile))
141 DeleteFile(AROS_BSTR_ADDR(cli->cli_CommandFile));
142 AROS_BSTR_setstrlen(cli->cli_CommandFile, 0);
145 cli->cli_CurrentInput = cli->cli_StandardInput;
146 cli->cli_Background = IsInteractive(cli->cli_CurrentInput) ? DOSFALSE : DOSTRUE;
148 setInteractive(cli, ss);
152 /* As per AmigaMail Vol 2, "II-65: Writing a UserShell",
153 * if we were running a SYSTEM command, we're done now.
155 moreLeft = cli->cli_Interactive;
157 if (cli->cli_Interactive) {
158 D(bug("Shell %ld: Flushing output 0x%lx, error 0x%lx\n",
159 ss->cliNumber, Output(), ErrorOutput()));
160 Flush(Output());
161 Flush(ErrorOutput());
163 } while (moreLeft);
165 bufferFree(&in, SysBase);
166 bufferFree(&out, SysBase);
168 return error;
172 /* Take care of one command line */
173 LONG checkLine(ShellState *ss, Buffer *in, Buffer *out, BOOL echo)
175 struct CommandLineInterface *cli = Cli();
176 BOOL haveCommand = FALSE;
177 LONG result;
179 result = convertLine(ss, in, out, &haveCommand);
181 if (result == 0)
183 D(bug("convertLine: haveCommand = %d, out->buf=%s\n", haveCommand,
184 out->buf));
185 /* Only a comment or dot command ? */
186 if (haveCommand == FALSE)
187 goto exit;
189 if (echo)
190 cliEcho(ss, out->buf);
192 /* OK, we've got a command. Let's execute it! */
193 result = executeLine(ss, out->buf);
196 /* If the command changed the cli's definition
197 * of cli_StandardInput/StandardOutput, let's
198 * reflect that.
200 * This also stops redirection, so that command errors go to
201 * the console instead of the redirected file.
203 SelectInput(cli->cli_StandardInput);
204 SelectOutput(cli->cli_StandardOutput);
206 if (result)
208 D(bug("convertLine: error = %ld faillevel=%ld\n", result,
209 cli->cli_FailLevel));
210 cli->cli_Result2 = result;
213 exit:
214 /* FIXME error handling is bullshit */
216 cliVarNum(ss, "RC", cli->cli_ReturnCode);
217 cliVarNum(ss, "Result2", cli->cli_Result2);
219 if (cli->cli_Interactive)
221 Flush(ErrorOutput());
222 Flush(Output());
225 return result;
228 /* Function: unloadCommand
230 * Action: Free the resources held by a (loaded) command.
232 * Input: ShellState *ss -- this state
233 * BPTR commandSeg -- segment of the program to unload
234 * BOOL homeDirChanged -- home changed flag
236 * Output: --
238 static void unloadCommand(ShellState *ss, BPTR commandSeg,
239 BOOL homeDirChanged, BOOL residentCommand)
241 struct CommandLineInterface *cli = Cli();
243 if (homeDirChanged)
244 UnLock(SetProgramDir(ss->oldHomeDir));
246 SetProgramName("");
248 if (!cli->cli_Module)
249 return;
251 if (residentCommand)
253 struct Segment *residentSeg = (struct Segment *)BADDR(commandSeg);
255 Forbid();
257 /* Decrease usecount */
258 if (residentSeg->seg_UC > 0)
259 residentSeg->seg_UC--;
261 Permit();
263 else
264 UnLoadSeg(commandSeg);
266 cli->cli_Module = BNULL;
269 /* Function: loadCommand
271 * Action: Load a command, searching the resident lists, paths and C:
273 * Input: ShellState *ss -- this state
274 * STRPTR commandName -- the command to load
275 * BOOL *homeDirChanged -- home changed result
276 * BPTR *scriptLock -- lock of script if one
278 * Output: BPTR -- segment of the loaded command or NULL if there was an
279 * error
281 static BPTR loadCommand(ShellState *ss, STRPTR commandName, BPTR *scriptLock,
282 BOOL *homeDirChanged, BOOL *residentCommand)
284 struct CommandLineInterface *cli = Cli();
285 BPTR oldCurDir;
286 BPTR commandSeg = BNULL;
287 BPTR *paths;
288 struct Segment *residentSeg;
289 BOOL absolutePath = strpbrk(commandName, "/:") != NULL;
290 BPTR file;
291 LONG err = 0;
293 /* We check the resident lists only if we do not have an absolute path */
294 if (!absolutePath)
296 Forbid();
298 /* Check regular list first... */
299 residentSeg = FindSegment(commandName, NULL, FALSE);
301 if (residentSeg == NULL)
303 /* ... then the system list */
304 residentSeg = FindSegment(commandName, NULL, TRUE);
307 if (residentSeg != NULL)
309 /* Can we use this command? */
310 if (residentSeg->seg_UC == CMD_INTERNAL || residentSeg->seg_UC >= 0)
312 if (residentSeg->seg_UC >= 0)
313 residentSeg->seg_UC++;
315 Permit();
316 *residentCommand = TRUE;
317 return MKBADDR(residentSeg);
321 Permit();
324 oldCurDir = CurrentDir(BNULL);
325 CurrentDir(oldCurDir);
327 file = Open(commandName, MODE_OLDFILE);
328 err = IoErr();
330 if (!file)
334 absolutePath || /* If this was an absolute path, we don't check the paths set by
335 'path' or the C: multiassign */
336 err == ERROR_OBJECT_IN_USE /* The object might be exclusively locked */
338 return BNULL;
340 /* Search the command in the path */
343 paths = (BPTR *)BADDR(cli->cli_CommandDir);
344 file == BNULL && paths != NULL;
345 paths = (BPTR *)BADDR(paths[0]) /* Go on with the next path */
348 CurrentDir(paths[1]);
349 file = Open(commandName, MODE_OLDFILE);
352 /* The last resort -- the C: multiassign */
353 if (!file)
355 commandName -= 2;
356 file = Open(commandName, MODE_OLDFILE);
360 if (file)
362 commandSeg = LoadSeg(commandName);
363 err = IoErr();
365 if (commandSeg)
367 BPTR lock = ParentOfFH(file);
369 if (lock)
371 ss->oldHomeDir = SetProgramDir(lock);
372 *homeDirChanged = TRUE; /* TODO merge */
375 /* Do not attempt to execute corrupted executables (BAD_HUNK)
376 * Do not swallow original error code */
377 if (!commandSeg && err == ERROR_NOT_EXECUTABLE) {
378 struct FileInfoBlock *fib = AllocDosObject(DOS_FIB, NULL);
379 if (fib && ExamineFH(file, fib) && (fib->fib_Protection & FIBF_SCRIPT))
381 commandSeg = LoadSeg("C:Execute");
382 if (commandSeg) {
383 *scriptLock = Lock(commandName, SHARED_LOCK);
384 if (*scriptLock == BNULL) {
385 UnLoadSeg(commandSeg);
386 commandSeg = BNULL;
390 else
391 err = ERROR_FILE_NOT_OBJECT;
392 FreeDosObject(DOS_FIB, fib);
395 Close(file);
396 } else
397 err = IoErr();
399 CurrentDir(oldCurDir);
400 SetIoErr(err);
402 return commandSeg;
405 /* Function: executeLine
407 * Action: Execute one line of commands
409 * Input: ShellState *ss -- this state
410 * STRPTR commandArgs -- arguments of the 'command'
412 * Output: LONG -- error code or 0 if everything went OK
414 static LONG executeLine(ShellState *ss, STRPTR commandArgs)
416 struct CommandLineInterface *cli = Cli();
417 STRPTR command = ss->command + 2;
418 BOOL homeDirChanged = FALSE, residentCommand = FALSE;
419 BPTR module, scriptLock = BNULL;
420 LONG error = 0;
421 TEXT *cmd;
423 D(bug("[Shell] executeLine: %s %s\n", command, commandArgs));
425 cmd = AllocVec(4096 * sizeof(TEXT), MEMF_ANY);
426 if (!cmd) {
427 PrintFault(ERROR_NO_FREE_STORE, NULL);
428 return ERROR_NO_FREE_STORE;
431 module = loadCommand(ss, command, &scriptLock,
432 &homeDirChanged, &residentCommand);
434 /* Set command name even if we couldn't load the command to be able to
435 report errors correctly */
436 SetProgramName(command);
438 if (module)
440 struct Process *pr = (struct Process *) FindTask(NULL);
441 ULONG defaultStack = cli->cli_DefaultStack * CLI_DEFAULTSTACK_UNIT;
442 STRPTR oldtaskname = pr->pr_Task.tc_Node.ln_Name;
443 ULONG mem_before = 0;
444 ULONG sig_before = pr->pr_Task.tc_SigAlloc;
445 ULONG sig_after;
446 ULONG sigmask;
447 BYTE sigbit;
449 BPTR seglist = residentCommand ? ((struct Segment *)BADDR(module))->seg_Seg : module;
451 STRPTR dst = cmd, src;
452 LONG len = 0;
454 if (scriptLock)
456 *dst++ = '"';
457 if (NameFromLock(scriptLock, dst, FILE_MAX) == 0) {
458 error = IoErr(); /* bad FS handler ? */
459 goto errexit;
461 while (*++dst != '\0');
463 *dst++ = '"';
464 *dst++ = ' ';
465 UnLock(scriptLock);
466 len = dst - cmd;
469 src = commandArgs;
470 for (; src && *src != '\0'; ++dst, ++src, ++len)
471 *dst = *src;
472 *dst = '\0';
474 D(bug("[Shell] command loaded: len=%ld, args=%s\n", len, cmd));
475 SetIoErr(0); /* Clear error before we execute this command */
476 SetSignal(0, SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D);
478 cli->cli_Module = seglist;
479 pr->pr_Task.tc_Node.ln_Name = command;
481 mem_before = FindVar("__debug_mem", LV_VAR) ? AvailMem(MEMF_ANY) : 0;
482 cli->cli_ReturnCode = RunCommand(seglist, defaultStack, cmd, len);
483 error = (cli->cli_ReturnCode == RETURN_OK) ? 0 : IoErr();
485 /* Update the state of the cli_Interactive field */
486 setInteractive(cli, ss);
489 Check if running the command has changed signal bits of the Shell
490 process. If there is a difference the signals will be set or freed
491 to avoid that the Shell runs out of free signals.
493 sig_after = pr->pr_Task.tc_SigAlloc;
494 if (sig_before != sig_after)
496 for (sigbit = 0; sigbit < 32; sigbit++)
498 sigmask = 1L << sigbit;
499 if ((sig_before & sigmask) && !(sig_after & sigmask))
501 /* Command has deleted signal => set it */
502 Printf("*** '%s' returned with freed signal 0x%lx\n", command, sigmask);
503 AllocSignal(sigbit);
505 else if (!(sig_before & sigmask) && (sig_after & sigmask))
507 /* Command has set signal => free it */
508 Printf("*** '%s' returned with unfreed signal 0x%lx\n", command, sigmask);
509 FreeSignal(sigbit);
514 if (mem_before)
516 ULONG mem_after = AvailMem(MEMF_ANY);
517 Printf("Memory leak of %lu bytes\n", mem_before - mem_after);
520 D(bug("[Shell] returned %ld (%ld): %s\n", cli->cli_ReturnCode,
521 error, command));
522 pr->pr_Task.tc_Node.ln_Name = oldtaskname;
523 unloadCommand(ss, module, homeDirChanged, residentCommand);
525 /* If command couldn't be started, explain why */
526 if (cli->cli_ReturnCode == -1)
527 PrintFault(error, command);
529 else
531 /* SFS returns ERROR_INVALID_COMPONENT_NAME if you try to open "" */
532 error = IoErr();
534 if (error == ERROR_OBJECT_WRONG_TYPE ||
535 error == ERROR_OBJECT_NOT_FOUND ||
536 error == ERROR_INVALID_COMPONENT_NAME)
538 BPTR lock = Lock(command, SHARED_LOCK);
540 if (lock)
542 struct FileInfoBlock *fib = AllocDosObject(DOS_FIB, NULL);
544 if (fib)
546 if (Examine(lock, fib))
548 if (fib->fib_DirEntryType > 0)
550 setPath(ss, lock);
551 lock = CurrentDir(lock);
553 else
554 SetIoErr(ERROR_OBJECT_WRONG_TYPE);
557 FreeDosObject(DOS_FIB, fib);
560 error = IoErr();
562 /* UnLock the old currentdir */
563 UnLock(lock);
566 PrintFault(error, command);
568 errexit:
569 FreeVec(cmd);
570 return error;
573 void setPath(ShellState *ss, BPTR lock)
575 BPTR dir;
576 STRPTR buf;
577 ULONG i = 0;
579 if (lock)
580 dir = lock;
581 else
582 dir = CurrentDir(BNULL);
586 i += 256;
587 buf = AllocVec(i, MEMF_ANY);
589 if (buf == NULL)
590 break;
592 if (NameFromLock(dir, buf, i))
594 SetCurrentDirName(buf);
595 FreeVec(buf);
596 break;
599 FreeVec(buf);
600 } while (IoErr() == ERROR_LINE_TOO_LONG);
602 if (lock == BNULL)
603 CurrentDir(dir);
606 #undef SysBase
607 #undef DOSBase
609 __startup AROS_CLI(ShellStart)
611 LONG error;
612 ShellState *ss;
613 APTR DOSBase;
614 struct Process *me = (struct Process *)FindTask(NULL);
615 struct CommandLineInterface *cli;
617 DOSBase = OpenLibrary("dos.library",36);
618 if (!DOSBase)
619 return RETURN_FAIL;
621 D(bug("[Shell] executing\n"));
622 ss = AllocMem(sizeof(ShellState), MEMF_CLEAR);
623 if (!ss) {
624 SetIoErr(ERROR_NO_FREE_STORE);
625 CloseLibrary(DOSBase);
626 return RETURN_FAIL;
629 ss->ss_DOSBase = DOSBase;
630 ss->ss_SysBase = SysBase;
632 /* Cache the CLI flags, passed in by the AROS_CLI() macro */
633 ss->flags = AROS_CLI_Flags;
635 /* Select the input and output streams.
636 * We don't use CurrentInput here, as it may
637 * contain our script input.
639 * Note that if CurrentInput contained, for example:
640 * ECHO ?
641 * DIR
643 * The behaviour would be that ECHO would put its
644 * ReadArgs query onto StandardOutput, and expects
645 * its input on StandardInput, and then execute DIR.
647 * This is AOS compatible behavior. It would be
648 * incorrect to assume that ECHO would print "DIR" on
649 * StandardOutput
651 cli = Cli();
653 SelectInput(cli->cli_StandardInput);
654 SelectOutput(cli->cli_StandardOutput);
655 setPath(ss, BNULL);
657 ss->cliNumber = me->pr_TaskNum;
658 cliVarNum(ss, "process", ss->cliNumber);
660 initDefaultInterpreterState(ss);
662 if (AROS_CLI_Type == CLI_RUN) {
663 FPrintf(cli->cli_StandardError, "[CLI %ld]\n", me->pr_TaskNum);
665 if (AROS_CLI_Type == CLI_NEWCLI) {
666 FPrintf(cli->cli_StandardOutput, "New Shell process %ld\n", me->pr_TaskNum);
669 error = interact(ss);
671 D(bug("Shell %ld: exiting, error = %ld\n", ss->cliNumber, error));
673 if (ss->arg_rd)
674 FreeDosObject(DOS_RDARGS, ss->arg_rd);
676 FreeMem(ss, sizeof(ShellState));
678 /* Make sure Input(), Output() and pr_CES don't
679 * point to any dangling files.
681 SelectInput(BNULL);
682 SelectOutput(BNULL);
683 me->pr_CES = BNULL;
685 CloseLibrary(DOSBase);
687 return error ? RETURN_FAIL : RETURN_OK;