2 Copyright © 1995-2014, The AROS Development Team. All rights reserved.
8 /******************************************************************************
22 Start a shell (interactive or background).
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
38 Feb 2008 - initial support for .key/bra/ket/dot/dollar/default.
42 Resident Shell L:UserShell-Seg SYSTEM PURE ADD
44 Configures the default User Shell to this shell
50 Execute, NewShell, Run
54 The prompt support does not use SetCurrentDirName() as this function
55 has improper limitations. More or less the same goes for GetProgramName().
57 ******************************************************************************/
60 Break support (and +(0L) before execution) -- CreateNewProc()?
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>
76 #include <aros/debug.h>
80 /* Prevent inclusion of the ErrorOutput() linklib
81 * routine from -lamiga, so that we don't need a global
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
;
122 setInteractive(cli
, ss
);
124 /* pre-allocate input buffer */
125 if ((error
= bufferAppend("?", 1, &in
, SysBase
))) /* FIXME drop when readLine ok */
129 if ((error
= Redirection_init(ss
)) == 0)
133 bufferReset(&in
); /* reuse allocated buffers */
136 D(bug("Shell %ld: Reading in a line of input...\n",
138 error
= readLine(ss
, cli
, &in
, &moreLeft
);
139 D(bug("Shell %ld: moreLeft=%d, error=%ld, Line is: "
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
)
155 if (!cli
->cli_Interactive
)
157 if (cli
->cli_ReturnCode
>= cli
->cli_FailLevel
) {
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");
169 Redirection_release(ss
);
175 popInterpreterState(ss
);
177 if (cli
->cli_Interactive
)
179 Printf("Process %ld ending\n", ss
->cliNumber
);
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
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()));
214 Flush(ErrorOutput());
218 bufferFree(&in
, SysBase
);
219 bufferFree(&out
, SysBase
);
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
;
232 result
= convertLine(ss
, in
, out
, &haveCommand
);
236 D(bug("convertLine: haveCommand = %d, out->buf=%s\n", haveCommand
,
238 /* Only a comment or dot command ? */
239 if (haveCommand
== FALSE
)
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
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
);
261 D(bug("convertLine: error = %ld faillevel=%ld\n", result
,
262 cli
->cli_FailLevel
));
263 cli
->cli_Result2
= result
;
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());
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
291 static void unloadCommand(ShellState
*ss
, BPTR commandSeg
,
292 BOOL homeDirChanged
, BOOL residentCommand
)
294 struct CommandLineInterface
*cli
= Cli();
297 UnLock(SetProgramDir(ss
->oldHomeDir
));
301 if (!cli
->cli_Module
)
306 struct Segment
*residentSeg
= (struct Segment
*)BADDR(commandSeg
);
310 /* Decrease usecount */
311 if (residentSeg
->seg_UC
> 0)
312 residentSeg
->seg_UC
--;
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
334 static BPTR
loadCommand(ShellState
*ss
, STRPTR commandName
, BPTR
*scriptLock
,
335 BOOL
*homeDirChanged
, BOOL
*residentCommand
)
337 struct CommandLineInterface
*cli
= Cli();
339 BPTR commandSeg
= BNULL
;
341 struct Segment
*residentSeg
;
342 BOOL absolutePath
= strpbrk(commandName
, "/:") != NULL
;
346 /* We check the resident lists only if we do not have an absolute path */
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
++;
369 *residentCommand
= TRUE
;
370 return MKBADDR(residentSeg
);
377 oldCurDir
= CurrentDir(BNULL
);
378 CurrentDir(oldCurDir
);
380 file
= Open(commandName
, MODE_OLDFILE
);
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 */
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 */
409 file
= Open(commandName
, MODE_OLDFILE
);
415 commandSeg
= LoadSeg(commandName
);
420 BPTR lock
= ParentOfFH(file
);
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");
436 *scriptLock
= Lock(commandName
, SHARED_LOCK
);
437 if (*scriptLock
== BNULL
) {
438 UnLoadSeg(commandSeg
);
444 err
= ERROR_FILE_NOT_OBJECT
;
445 FreeDosObject(DOS_FIB
, fib
);
452 CurrentDir(oldCurDir
);
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
;
476 D(bug("[Shell] executeLine: %s %s\n", command
, commandArgs
));
478 cmd
= AllocVec(4096 * sizeof(TEXT
), MEMF_ANY
);
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
);
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
;
502 BPTR seglist
= residentCommand
? ((struct Segment
*)BADDR(module
))->seg_Seg
: module
;
504 STRPTR dst
= cmd
, src
;
510 if (NameFromLock(scriptLock
, dst
, FILE_MAX
) == 0) {
511 error
= IoErr(); /* bad FS handler ? */
514 while (*++dst
!= '\0');
523 for (; src
&& *src
!= '\0'; ++dst
, ++src
, ++len
)
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
);
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
);
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
);
580 /* SFS returns ERROR_INVALID_COMPONENT_NAME if you try to open "" */
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
);
591 struct FileInfoBlock
*fib
= AllocDosObject(DOS_FIB
, NULL
);
595 if (Examine(lock
, fib
))
597 if (fib
->fib_DirEntryType
> 0)
600 lock
= CurrentDir(lock
);
603 SetIoErr(ERROR_OBJECT_WRONG_TYPE
);
606 FreeDosObject(DOS_FIB
, fib
);
611 /* UnLock the old currentdir */
615 PrintFault(error
, command
);
622 void setPath(ShellState
*ss
, BPTR lock
)
631 dir
= CurrentDir(BNULL
);
636 buf
= AllocVec(i
, MEMF_ANY
);
641 if (NameFromLock(dir
, buf
, i
))
643 SetCurrentDirName(buf
);
649 } while (IoErr() == ERROR_LINE_TOO_LONG
);
658 __startup
AROS_CLI(ShellStart
)
663 struct Process
*me
= (struct Process
*)FindTask(NULL
);
664 struct CommandLineInterface
*cli
;
666 DOSBase
= OpenLibrary("dos.library",36);
670 D(bug("[Shell] executing\n"));
671 ss
= AllocMem(sizeof(ShellState
), MEMF_CLEAR
);
673 SetIoErr(ERROR_NO_FREE_STORE
);
674 CloseLibrary(DOSBase
);
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:
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
702 SelectInput(cli
->cli_StandardInput
);
703 SelectOutput(cli
->cli_StandardOutput
);
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
));
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.
734 CloseLibrary(DOSBase
);
736 return error
? RETURN_FAIL
: RETURN_OK
;