2 Copyright © 1995-2014, The AROS Development Team. All rights reserved.
7 Break support (and +(0L) before execution) -- CreateNewProc()?
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>
23 #include <aros/debug.h>
27 /* Prevent inclusion of the ErrorOutput() linklib
28 * routine from -lamiga, so that we don't need a global
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
;
69 setInteractive(cli
, ss
);
71 /* pre-allocate input buffer */
72 if ((error
= bufferAppend("?", 1, &in
, SysBase
))) /* FIXME drop when readLine ok */
76 if ((error
= Redirection_init(ss
)) == 0)
80 bufferReset(&in
); /* reuse allocated buffers */
83 D(bug("Shell %ld: Reading in a line of input...\n",
85 error
= readLine(ss
, cli
, &in
, &moreLeft
);
86 D(bug("Shell %ld: moreLeft=%d, error=%ld, Line is: "
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
)
102 if (!cli
->cli_Interactive
)
104 if (cli
->cli_ReturnCode
>= cli
->cli_FailLevel
) {
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");
116 Redirection_release(ss
);
122 popInterpreterState(ss
);
124 if (cli
->cli_Interactive
)
126 Printf("Process %ld ending\n", ss
->cliNumber
);
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
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()));
161 Flush(ErrorOutput());
165 bufferFree(&in
, SysBase
);
166 bufferFree(&out
, SysBase
);
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
;
179 result
= convertLine(ss
, in
, out
, &haveCommand
);
183 D(bug("convertLine: haveCommand = %d, out->buf=%s\n", haveCommand
,
185 /* Only a comment or dot command ? */
186 if (haveCommand
== FALSE
)
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
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
);
208 D(bug("convertLine: error = %ld faillevel=%ld\n", result
,
209 cli
->cli_FailLevel
));
210 cli
->cli_Result2
= result
;
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());
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
238 static void unloadCommand(ShellState
*ss
, BPTR commandSeg
,
239 BOOL homeDirChanged
, BOOL residentCommand
)
241 struct CommandLineInterface
*cli
= Cli();
244 UnLock(SetProgramDir(ss
->oldHomeDir
));
248 if (!cli
->cli_Module
)
253 struct Segment
*residentSeg
= (struct Segment
*)BADDR(commandSeg
);
257 /* Decrease usecount */
258 if (residentSeg
->seg_UC
> 0)
259 residentSeg
->seg_UC
--;
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
281 static BPTR
loadCommand(ShellState
*ss
, STRPTR commandName
, BPTR
*scriptLock
,
282 BOOL
*homeDirChanged
, BOOL
*residentCommand
)
284 struct CommandLineInterface
*cli
= Cli();
286 BPTR commandSeg
= BNULL
;
288 struct Segment
*residentSeg
;
289 BOOL absolutePath
= strpbrk(commandName
, "/:") != NULL
;
293 /* We check the resident lists only if we do not have an absolute path */
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
++;
316 *residentCommand
= TRUE
;
317 return MKBADDR(residentSeg
);
324 oldCurDir
= CurrentDir(BNULL
);
325 CurrentDir(oldCurDir
);
327 file
= Open(commandName
, MODE_OLDFILE
);
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 */
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 */
356 file
= Open(commandName
, MODE_OLDFILE
);
362 commandSeg
= LoadSeg(commandName
);
367 BPTR lock
= ParentOfFH(file
);
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");
383 *scriptLock
= Lock(commandName
, SHARED_LOCK
);
384 if (*scriptLock
== BNULL
) {
385 UnLoadSeg(commandSeg
);
391 err
= ERROR_FILE_NOT_OBJECT
;
392 FreeDosObject(DOS_FIB
, fib
);
399 CurrentDir(oldCurDir
);
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
;
423 D(bug("[Shell] executeLine: %s %s\n", command
, commandArgs
));
425 cmd
= AllocVec(4096 * sizeof(TEXT
), MEMF_ANY
);
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
);
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
;
449 BPTR seglist
= residentCommand
? ((struct Segment
*)BADDR(module
))->seg_Seg
: module
;
451 STRPTR dst
= cmd
, src
;
457 if (NameFromLock(scriptLock
, dst
, FILE_MAX
) == 0) {
458 error
= IoErr(); /* bad FS handler ? */
461 while (*++dst
!= '\0');
470 for (; src
&& *src
!= '\0'; ++dst
, ++src
, ++len
)
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
);
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
);
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
,
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
);
531 /* SFS returns ERROR_INVALID_COMPONENT_NAME if you try to open "" */
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
);
542 struct FileInfoBlock
*fib
= AllocDosObject(DOS_FIB
, NULL
);
546 if (Examine(lock
, fib
))
548 if (fib
->fib_DirEntryType
> 0)
551 lock
= CurrentDir(lock
);
554 SetIoErr(ERROR_OBJECT_WRONG_TYPE
);
557 FreeDosObject(DOS_FIB
, fib
);
562 /* UnLock the old currentdir */
566 PrintFault(error
, command
);
573 void setPath(ShellState
*ss
, BPTR lock
)
582 dir
= CurrentDir(BNULL
);
587 buf
= AllocVec(i
, MEMF_ANY
);
592 if (NameFromLock(dir
, buf
, i
))
594 SetCurrentDirName(buf
);
600 } while (IoErr() == ERROR_LINE_TOO_LONG
);
609 __startup
AROS_CLI(ShellStart
)
614 struct Process
*me
= (struct Process
*)FindTask(NULL
);
615 struct CommandLineInterface
*cli
;
618 DOSBase
= OpenLibrary("dos.library",36);
622 D(bug("[Shell] executing\n"));
623 ss
= AllocMem(sizeof(ShellState
), MEMF_CLEAR
);
625 SetIoErr(ERROR_NO_FREE_STORE
);
626 CloseLibrary(DOSBase
);
630 ss
->ss_DOSBase
= DOSBase
;
631 ss
->ss_SysBase
= SysBase
;
633 /* Cache the CLI flags, passed in by the AROS_CLI() macro */
634 ss
->flags
= AROS_CLI_Flags
;
636 /* Select the input and output streams.
637 * We don't use CurrentInput here, as it may
638 * contain our script input.
640 * Note that if CurrentInput contained, for example:
644 * The behaviour would be that ECHO would put its
645 * ReadArgs query onto StandardOutput, and expects
646 * its input on StandardInput, and then execute DIR.
648 * This is AOS compatible behavior. It would be
649 * incorrect to assume that ECHO would print "DIR" on
654 SelectInput(cli
->cli_StandardInput
);
655 SelectOutput(cli
->cli_StandardOutput
);
658 ss
->cliNumber
= me
->pr_TaskNum
;
659 cliVarNum(ss
, "process", ss
->cliNumber
);
661 initDefaultInterpreterState(ss
);
663 prntArgs
[0] = me
->pr_TaskNum
;
664 if (AROS_CLI_Type
== CLI_RUN
) {
665 VFPrintf(cli
->cli_StandardError
, "[CLI %ld]\n", (RAWARG
)prntArgs
);
667 if (AROS_CLI_Type
== CLI_NEWCLI
) {
668 VFPrintf(cli
->cli_StandardOutput
, "New Shell process %ld\n", (RAWARG
)prntArgs
);
671 error
= interact(ss
);
673 D(bug("Shell %ld: exiting, error = %ld\n", ss
->cliNumber
, error
));
676 FreeDosObject(DOS_RDARGS
, ss
->arg_rd
);
678 FreeMem(ss
, sizeof(ShellState
));
680 /* Make sure Input(), Output() and pr_CES don't
681 * point to any dangling files.
687 CloseLibrary(DOSBase
);
689 return error
? RETURN_FAIL
: RETURN_OK
;