Made executeLine() static.
[AROS.git] / workbench / c / Shell / Shell.c
blob2800be75bdf13a5d39fff11943cf58bdee1a889b
1 /*
2 Copyright © 1995-2014, The AROS Development Team. All rights reserved.
3 $Id$
5 The shell program.
6 */
8 /******************************************************************************
10 NAME
12 Shell
14 SYNOPSIS
16 LOCATION
18 L:UserShell-Seg
20 FUNCTION
22 Start a shell (interactive or background).
24 INPUTS
26 RESULT
28 HISTORY
30 Aug 2011 - Use AOS startup packet mechanisms
32 Sep 2010 - rewrite of the convertLine function.
34 Jul 2010 - improved handling of $ and `: things like cd SYS:Olle/$pelle
35 work now. Non-alphanumerical var-names must be enclosed in
36 braces.
38 Feb 2008 - initial support for .key/bra/ket/dot/dollar/default.
40 EXAMPLE
42 Resident Shell L:UserShell-Seg SYSTEM PURE ADD
44 Configures the default User Shell to this shell
46 BUGS
48 SEE ALSO
50 Execute, NewShell, Run
52 INTERNALS
54 The prompt support does not use SetCurrentDirName() as this function
55 has improper limitations. More or less the same goes for GetProgramName().
57 ******************************************************************************/
59 /* TODO:
60 Break support (and +(0L) before execution) -- CreateNewProc()?
63 #define DEBUG 0
64 #include <dos/dos.h>
65 #include <dos/stdio.h>
66 #include <dos/cliinit.h>
67 #include <exec/libraries.h>
68 #include <exec/lists.h>
69 #include <libraries/expansionbase.h>
70 #include <proto/alib.h>
71 #include <proto/dos.h>
72 #include <proto/exec.h>
74 #include <ctype.h>
76 #include <aros/debug.h>
78 #include "Shell.h"
80 /* Prevent inclusion of the ErrorOutput() linklib
81 * routine from -lamiga, so that we don't need a global
82 * SysBase
84 #define ErrorOutput() (((struct Process *)FindTask(NULL))->pr_CES)
86 #define IS_SYSTEM ((ss->flags & (FNF_VALIDFLAGS | FNF_SYSTEM)) == (FNF_VALIDFLAGS | FNF_SYSTEM))
87 #define IS_SCRIPT (cli->cli_CurrentInput != cli->cli_StandardInput)
89 static LONG executeLine(ShellState *ss, STRPTR commandArgs);
91 BOOL setInteractive(struct CommandLineInterface *cli, ShellState *ss)
93 D(bug("Shell %ld: Flags = 0x%lx\n", ss->cliNumber, ss->flags));
94 D(bug("Shell %ld: cli_Interactive = %ld\n", ss->cliNumber,
95 cli->cli_Interactive));
96 D(bug("Shell %ld: cli_Background = %ld\n", ss->cliNumber,
97 cli->cli_Background));
98 D(bug("Shell %ld: cli_CurrentInput = %p\n", ss->cliNumber,
99 cli->cli_CurrentInput));
100 D(bug("Shell %ld: cli_StandardInput = %p\n", ss->cliNumber,
101 cli->cli_StandardInput));
102 if (!cli->cli_Background && IS_SCRIPT)
103 cli->cli_Background = DOSTRUE;
105 cli->cli_Interactive = (cli->cli_Background || IS_SCRIPT || IS_SYSTEM) ? DOSFALSE : DOSTRUE;
106 D(bug("Shell %ld: cli_Interactive => %ld\n", ss->cliNumber,
107 cli->cli_Interactive));
108 D(bug("Shell %ld: cli_Background => %ld\n", ss->cliNumber,
109 cli->cli_Background));
111 return cli->cli_Interactive;
114 /* First we execute the script, then we interact with the user */
115 LONG interact(ShellState *ss)
117 struct CommandLineInterface *cli = Cli();
118 Buffer in = {0}, out = {0};
119 BOOL moreLeft = FALSE;
120 LONG error = 0;
122 setInteractive(cli, ss);
124 /* pre-allocate input buffer */
125 if ((error = bufferAppend("?", 1, &in, SysBase))) /* FIXME drop when readLine ok */
126 return error;
128 do {
129 if ((error = Redirection_init(ss)) == 0)
131 cliPrompt(ss);
133 bufferReset(&in); /* reuse allocated buffers */
134 bufferReset(&out);
136 D(bug("Shell %ld: Reading in a line of input...\n",
137 ss->cliNumber));
138 error = readLine(ss, cli, &in, &moreLeft);
139 D(bug("Shell %ld: moreLeft=%d, error=%ld, Line is: "
140 "%ld bytes (%s)\n",
141 ss->cliNumber, moreLeft, error, in.len, in.buf));
143 if (error == 0 && in.len > 0)
144 error = checkLine(ss, &in, &out, TRUE);
146 /* The command may have modified cli_Background.
147 * C:Execute does that.
149 setInteractive(cli, ss);
151 /* As per AmigaMail Vol 2, "II-65: Writing a UserShell" */
152 if (IS_SYSTEM && !IS_SCRIPT)
153 moreLeft = FALSE;
155 if (!cli->cli_Interactive)
157 if (cli->cli_ReturnCode >= cli->cli_FailLevel) {
158 moreLeft = FALSE;
159 D(bug("Shell: cli_ReturnCode (%ld) >= cli->cli_FailLevel (%ld)\n", cli->cli_ReturnCode, cli->cli_FailLevel));
162 if (CheckSignal(SIGBREAKF_CTRL_D))
164 PrintFault(ERROR_BREAK, "Shell");
165 moreLeft = FALSE;
169 Redirection_release(ss);
172 if (moreLeft)
173 continue;
175 popInterpreterState(ss);
177 if (cli->cli_Interactive)
179 Printf("Process %ld ending\n", ss->cliNumber);
180 Flush(Output());
181 break;
184 if (IS_SCRIPT) {
185 D(bug("Shell %ld: Closing CLI input 0x%08lx\n", ss->cliNumber,
186 cli->cli_CurrentInput));
187 Close(cli->cli_CurrentInput);
189 /* Now that we've closed CurrentInput, we can delete
190 * the CommandFile.
192 if (AROS_BSTR_strlen(cli->cli_CommandFile))
194 DeleteFile(AROS_BSTR_ADDR(cli->cli_CommandFile));
195 AROS_BSTR_setstrlen(cli->cli_CommandFile, 0);
198 cli->cli_CurrentInput = cli->cli_StandardInput;
199 cli->cli_Background = IsInteractive(cli->cli_CurrentInput) ? DOSFALSE : DOSTRUE;
201 setInteractive(cli, ss);
205 /* As per AmigaMail Vol 2, "II-65: Writing a UserShell",
206 * if we were running a SYSTEM command, we're done now.
208 moreLeft = cli->cli_Interactive;
210 if (cli->cli_Interactive) {
211 D(bug("Shell %ld: Flushing output 0x%lx, error 0x%lx\n",
212 ss->cliNumber, Output(), ErrorOutput()));
213 Flush(Output());
214 Flush(ErrorOutput());
216 } while (moreLeft);
218 bufferFree(&in, SysBase);
219 bufferFree(&out, SysBase);
221 return error;
225 /* Take care of one command line */
226 LONG checkLine(ShellState *ss, Buffer *in, Buffer *out, BOOL echo)
228 struct CommandLineInterface *cli = Cli();
229 BOOL haveCommand = FALSE;
230 LONG result;
232 result = convertLine(ss, in, out, &haveCommand);
234 if (result == 0)
236 D(bug("convertLine: haveCommand = %d, out->buf=%s\n", haveCommand,
237 out->buf));
238 /* Only a comment or dot command ? */
239 if (haveCommand == FALSE)
240 goto exit;
242 if (echo)
243 cliEcho(ss, out->buf);
245 /* OK, we've got a command. Let's execute it! */
246 result = executeLine(ss, out->buf);
249 /* If the command changed the cli's definition
250 * of cli_StandardInput/StandardOutput, let's
251 * reflect that.
253 * This also stops redirection, so that command errors go to
254 * the console instead of the redirected file.
256 SelectInput(cli->cli_StandardInput);
257 SelectOutput(cli->cli_StandardOutput);
259 if (result)
261 D(bug("convertLine: error = %ld faillevel=%ld\n", result,
262 cli->cli_FailLevel));
263 cli->cli_Result2 = result;
266 exit:
267 /* FIXME error handling is bullshit */
269 cliVarNum(ss, "RC", cli->cli_ReturnCode);
270 cliVarNum(ss, "Result2", cli->cli_Result2);
272 if (cli->cli_Interactive)
274 Flush(ErrorOutput());
275 Flush(Output());
278 return result;
281 /* Function: unloadCommand
283 * Action: Free the resources held by a (loaded) command.
285 * Input: ShellState *ss -- this state
286 * BPTR commandSeg -- segment of the program to unload
287 * BOOL homeDirChanged -- home changed flag
289 * Output: --
291 static void unloadCommand(ShellState *ss, BPTR commandSeg,
292 BOOL homeDirChanged, BOOL residentCommand)
294 struct CommandLineInterface *cli = Cli();
296 if (homeDirChanged)
297 UnLock(SetProgramDir(ss->oldHomeDir));
299 SetProgramName("");
301 if (!cli->cli_Module)
302 return;
304 if (residentCommand)
306 struct Segment *residentSeg = (struct Segment *)BADDR(commandSeg);
308 Forbid();
310 /* Decrease usecount */
311 if (residentSeg->seg_UC > 0)
312 residentSeg->seg_UC--;
314 Permit();
316 else
317 UnLoadSeg(commandSeg);
319 cli->cli_Module = BNULL;
322 /* Function: loadCommand
324 * Action: Load a command, searching the resident lists, paths and C:
326 * Input: ShellState *ss -- this state
327 * STRPTR commandName -- the command to load
328 * BOOL *homeDirChanged -- home changed result
329 * BPTR *scriptLock -- lock of script if one
331 * Output: BPTR -- segment of the loaded command or NULL if there was an
332 * error
334 static BPTR loadCommand(ShellState *ss, STRPTR commandName, BPTR *scriptLock,
335 BOOL *homeDirChanged, BOOL *residentCommand)
337 struct CommandLineInterface *cli = Cli();
338 BPTR oldCurDir;
339 BPTR commandSeg = BNULL;
340 BPTR *paths;
341 struct Segment *residentSeg;
342 BOOL absolutePath = strpbrk(commandName, "/:") != NULL;
343 BPTR file;
344 LONG err = 0;
346 /* We check the resident lists only if we do not have an absolute path */
347 if (!absolutePath)
349 Forbid();
351 /* Check regular list first... */
352 residentSeg = FindSegment(commandName, NULL, FALSE);
354 if (residentSeg == NULL)
356 /* ... then the system list */
357 residentSeg = FindSegment(commandName, NULL, TRUE);
360 if (residentSeg != NULL)
362 /* Can we use this command? */
363 if (residentSeg->seg_UC == CMD_INTERNAL || residentSeg->seg_UC >= 0)
365 if (residentSeg->seg_UC >= 0)
366 residentSeg->seg_UC++;
368 Permit();
369 *residentCommand = TRUE;
370 return MKBADDR(residentSeg);
374 Permit();
377 oldCurDir = CurrentDir(BNULL);
378 CurrentDir(oldCurDir);
380 file = Open(commandName, MODE_OLDFILE);
381 err = IoErr();
383 if (!file)
387 absolutePath || /* If this was an absolute path, we don't check the paths set by
388 'path' or the C: multiassign */
389 err == ERROR_OBJECT_IN_USE /* The object might be exclusively locked */
391 return BNULL;
393 /* Search the command in the path */
396 paths = (BPTR *)BADDR(cli->cli_CommandDir);
397 file == BNULL && paths != NULL;
398 paths = (BPTR *)BADDR(paths[0]) /* Go on with the next path */
401 CurrentDir(paths[1]);
402 file = Open(commandName, MODE_OLDFILE);
405 /* The last resort -- the C: multiassign */
406 if (!file)
408 commandName -= 2;
409 file = Open(commandName, MODE_OLDFILE);
413 if (file)
415 commandSeg = LoadSeg(commandName);
416 err = IoErr();
418 if (commandSeg)
420 BPTR lock = ParentOfFH(file);
422 if (lock)
424 ss->oldHomeDir = SetProgramDir(lock);
425 *homeDirChanged = TRUE; /* TODO merge */
428 /* Do not attempt to execute corrupted executables (BAD_HUNK)
429 * Do not swallow original error code */
430 if (!commandSeg && err == ERROR_NOT_EXECUTABLE) {
431 struct FileInfoBlock *fib = AllocDosObject(DOS_FIB, NULL);
432 if (fib && ExamineFH(file, fib) && (fib->fib_Protection & FIBF_SCRIPT))
434 commandSeg = LoadSeg("C:Execute");
435 if (commandSeg) {
436 *scriptLock = Lock(commandName, SHARED_LOCK);
437 if (*scriptLock == BNULL) {
438 UnLoadSeg(commandSeg);
439 commandSeg = BNULL;
443 else
444 err = ERROR_FILE_NOT_OBJECT;
445 FreeDosObject(DOS_FIB, fib);
448 Close(file);
449 } else
450 err = IoErr();
452 CurrentDir(oldCurDir);
453 SetIoErr(err);
455 return commandSeg;
458 /* Function: executeLine
460 * Action: Execute one line of commands
462 * Input: ShellState *ss -- this state
463 * STRPTR commandArgs -- arguments of the 'command'
465 * Output: LONG -- error code or 0 if everything went OK
467 static LONG executeLine(ShellState *ss, STRPTR commandArgs)
469 struct CommandLineInterface *cli = Cli();
470 STRPTR command = ss->command + 2;
471 BOOL homeDirChanged = FALSE, residentCommand = FALSE;
472 BPTR module, scriptLock = BNULL;
473 LONG error = 0;
474 TEXT *cmd;
476 D(bug("[Shell] executeLine: %s %s\n", command, commandArgs));
478 cmd = AllocVec(4096 * sizeof(TEXT), MEMF_ANY);
479 if (!cmd) {
480 PrintFault(ERROR_NO_FREE_STORE, NULL);
481 return ERROR_NO_FREE_STORE;
484 module = loadCommand(ss, command, &scriptLock,
485 &homeDirChanged, &residentCommand);
487 /* Set command name even if we couldn't load the command to be able to
488 report errors correctly */
489 SetProgramName(command);
491 if (module)
493 struct Process *pr = (struct Process *) FindTask(NULL);
494 ULONG defaultStack = cli->cli_DefaultStack * CLI_DEFAULTSTACK_UNIT;
495 STRPTR oldtaskname = pr->pr_Task.tc_Node.ln_Name;
496 ULONG mem_before = 0;
497 ULONG sig_before = pr->pr_Task.tc_SigAlloc;
498 ULONG sig_after;
499 ULONG sigmask;
500 BYTE sigbit;
502 BPTR seglist = residentCommand ? ((struct Segment *)BADDR(module))->seg_Seg : module;
504 STRPTR dst = cmd, src;
505 LONG len = 0;
507 if (scriptLock)
509 *dst++ = '"';
510 if (NameFromLock(scriptLock, dst, FILE_MAX) == 0) {
511 error = IoErr(); /* bad FS handler ? */
512 goto errexit;
514 while (*++dst != '\0');
516 *dst++ = '"';
517 *dst++ = ' ';
518 UnLock(scriptLock);
519 len = dst - cmd;
522 src = commandArgs;
523 for (; src && *src != '\0'; ++dst, ++src, ++len)
524 *dst = *src;
525 *dst = '\0';
527 D(bug("[Shell] command loaded: len=%ld, args=%s\n", len, cmd));
528 SetIoErr(0); /* Clear error before we execute this command */
529 SetSignal(0, SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D);
531 cli->cli_Module = seglist;
532 pr->pr_Task.tc_Node.ln_Name = command;
534 mem_before = FindVar("__debug_mem", LV_VAR) ? AvailMem(MEMF_ANY) : 0;
535 cli->cli_ReturnCode = RunCommand(seglist, defaultStack, cmd, len);
537 /* Update the state of the cli_Interactive field */
538 setInteractive(cli, ss);
541 Check if running the command has changed signal bits of the Shell
542 process. If there is a difference the signals will be set or freed
543 to avoid that the Shell runs out of free signals.
545 sig_after = pr->pr_Task.tc_SigAlloc;
546 if (sig_before != sig_after)
548 for (sigbit = 0; sigbit < 32; sigbit++)
550 sigmask = 1L << sigbit;
551 if ((sig_before & sigmask) && !(sig_after & sigmask))
553 /* Command has deleted signal => set it */
554 Printf("*** '%s' returned with freed signal 0x%lx\n", command, sigmask);
555 AllocSignal(sigbit);
557 else if (!(sig_before & sigmask) && (sig_after & sigmask))
559 /* Command has set signal => free it */
560 Printf("*** '%s' returned with unfreed signal 0x%lx\n", command, sigmask);
561 FreeSignal(sigbit);
566 if (mem_before)
568 ULONG mem_after = AvailMem(MEMF_ANY);
569 Printf("Memory leak of %lu bytes\n", mem_before - mem_after);
572 D(bug("[Shell] returned %ld (%ld): %s\n", cli->cli_ReturnCode, IoErr(), command));
573 error = (cli->cli_ReturnCode == RETURN_OK) ? 0 : IoErr();
574 pr->pr_Task.tc_Node.ln_Name = oldtaskname;
575 unloadCommand(ss, module, homeDirChanged, residentCommand);
578 else
580 /* SFS returns ERROR_INVALID_COMPONENT_NAME if you try to open "" */
581 error = IoErr();
583 if (error == ERROR_OBJECT_WRONG_TYPE ||
584 error == ERROR_OBJECT_NOT_FOUND ||
585 error == ERROR_INVALID_COMPONENT_NAME)
587 BPTR lock = Lock(command, SHARED_LOCK);
589 if (lock)
591 struct FileInfoBlock *fib = AllocDosObject(DOS_FIB, NULL);
593 if (fib)
595 if (Examine(lock, fib))
597 if (fib->fib_DirEntryType > 0)
599 setPath(ss, lock);
600 lock = CurrentDir(lock);
602 else
603 SetIoErr(ERROR_OBJECT_WRONG_TYPE);
606 FreeDosObject(DOS_FIB, fib);
609 error = IoErr();
611 /* UnLock the old currentdir */
612 UnLock(lock);
615 PrintFault(error, command);
617 errexit:
618 FreeVec(cmd);
619 return error;
622 void setPath(ShellState *ss, BPTR lock)
624 BPTR dir;
625 STRPTR buf;
626 ULONG i = 0;
628 if (lock)
629 dir = lock;
630 else
631 dir = CurrentDir(BNULL);
635 i += 256;
636 buf = AllocVec(i, MEMF_ANY);
638 if (buf == NULL)
639 break;
641 if (NameFromLock(dir, buf, i))
643 SetCurrentDirName(buf);
644 FreeVec(buf);
645 break;
648 FreeVec(buf);
649 } while (IoErr() == ERROR_LINE_TOO_LONG);
651 if (lock == BNULL)
652 CurrentDir(dir);
655 #undef SysBase
656 #undef DOSBase
658 __startup AROS_CLI(ShellStart)
660 LONG error;
661 ShellState *ss;
662 APTR DOSBase;
663 struct Process *me = (struct Process *)FindTask(NULL);
664 struct CommandLineInterface *cli;
666 DOSBase = OpenLibrary("dos.library",36);
667 if (!DOSBase)
668 return RETURN_FAIL;
670 D(bug("[Shell] executing\n"));
671 ss = AllocMem(sizeof(ShellState), MEMF_CLEAR);
672 if (!ss) {
673 SetIoErr(ERROR_NO_FREE_STORE);
674 CloseLibrary(DOSBase);
675 return RETURN_FAIL;
678 ss->ss_DOSBase = DOSBase;
679 ss->ss_SysBase = SysBase;
681 /* Cache the CLI flags, passed in by the AROS_CLI() macro */
682 ss->flags = AROS_CLI_Flags;
684 /* Select the input and output streams.
685 * We don't use CurrentInput here, as it may
686 * contain our script input.
688 * Note that if CurrentInput contained, for example:
689 * ECHO ?
690 * DIR
692 * The behaviour would be that ECHO would put its
693 * ReadArgs query onto StandardOutput, and expects
694 * its input on StandardInput, and then execute DIR.
696 * This is AOS compatible behavior. It would be
697 * incorrect to assume that ECHO would print "DIR" on
698 * StandardOutput
700 cli = Cli();
702 SelectInput(cli->cli_StandardInput);
703 SelectOutput(cli->cli_StandardOutput);
704 setPath(ss, BNULL);
706 ss->cliNumber = me->pr_TaskNum;
707 cliVarNum(ss, "process", ss->cliNumber);
709 initDefaultInterpreterState(ss);
711 if (AROS_CLI_Type == CLI_RUN) {
712 FPrintf(cli->cli_StandardError, "[CLI %ld]\n", me->pr_TaskNum);
714 if (AROS_CLI_Type == CLI_NEWCLI) {
715 FPrintf(cli->cli_StandardOutput, "New Shell process %ld\n", me->pr_TaskNum);
718 error = interact(ss);
720 D(bug("Shell %ld: exiting, error = %ld\n", ss->cliNumber, error));
722 if (ss->arg_rd)
723 FreeDosObject(DOS_RDARGS, ss->arg_rd);
725 FreeMem(ss, sizeof(ShellState));
727 /* Make sure Input(), Output() and pr_CES don't
728 * point to any dangling files.
730 SelectInput(BNULL);
731 SelectOutput(BNULL);
732 me->pr_CES = BNULL;
734 CloseLibrary(DOSBase);
736 return error ? RETURN_FAIL : RETURN_OK;