home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga Developer CD v1.2
/
amidev_cd_12.iso
/
reference
/
amiga_mail_vol2
/
ii-65
/
myshell.c
< prev
next >
Wrap
C/C++ Source or Header
|
1996-01-30
|
19KB
|
527 lines
;/* myshell.c - Execute to compile me with SAS/C 6.56 using pragmas
; (c) Copyright 1992 Commodore-Amiga, Inc. All rights reserved.
; The information contained herein is subject to change without notice,
; and is provided "as is" without warranty of any kind, either expressed
; or implied. The entire risk as to the use of this information is
; assumed by the user.
;
sc DATA=FAR NMINC STRMERGE NOSTKCHK CODE=NEAR PARMS=REG NODEBUG IGNORE=73 DEFINE=DOPRAGMAS myshell.c
slink FROM myshell.o TO myshell smallcode smalldata
quit
*/
/*
* This is a basically a skeleton of a UserShell. A UserShell is a special
* shell that can replace the default system shell. It has to meets some
* system requirements to function as a system shell. This example takes care
* of all of those requirements. To make this shell the system shell, use the
* resident command:
*
* resident shell MyShell SYSTEM
*
* Because this shell only serves as an minimal example of a UserShell and does
* not do many of the standard functions a shell normally performs. It is
* limited to the commands from the resident list (which would make it a bad
* idea to add this shell to the resident list if you need a useable default
* shell!)
*/
#include <exec/types.h>
#include <exec/memory.h>
#include <dos/dosextens.h>
#include <dos/stdio.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/alib_stdio_protos.h>
#ifdef DOPRAGMAS
#include <pragmas/exec_pragmas.h>
#include <pragmas/dos_pragmas.h>
#endif
long main(void);
#define COMMANDBUFLENGTH 64
#define COMMANDLINELENGTH 512
#define PROMPTLENGTH 256
/* True if this is a System() Instigated shell */
#define SYSTEM ((ml->fn & 0x80000004) == 0x80000004)
/* true if this shell is executing a script */
#define ISSCRIPT (ml->mycli->cli_CurrentInput != ml->mycli->cli_StandardInput)
/* true if this shell is *not* executing a script */
#define NOTSCRIPT (ml->mycli->cli_CurrentInput == ml->mycli->cli_StandardInput)
struct mylocals
{
struct Library *sysBase;
struct Library *dosBase;
struct Process *myprocess;
struct DosPacket *mypacket;
long fn; /* notice that fn is signed. Some conditionals
in this code rely on this value being signed.
*/
struct CommandLineInterface *mycli;
};
/*
* define the library base labels (SysBase and DOSBase)
* so we don't have to declare them as a global. Can't have global data in
* resident code.
*/
#define SysBase (ml->sysBase)
#define DOSBase (ml->dosBase)
long mainshellloop(struct mylocals *);
long strlen(UBYTE *);
long
main(void)
{
struct mylocals globals, *ml = &globals;
BPTR *segment;
long shelltype, error;
/*
* Poof, this shell has winked into existence. It could have come from the
* user executing the "newshell" code via the newshell program, or it could
* have come from some other application using one of the DOS calls that use
* a shell, like System(). In any case, whatever caused this shell to wink
* into existence will also cause a special startup packet to appear in this
* process' message port. This packet is very personal and private to DOS
* and probably will change with future versions of the OS, so don't worry
* about what's in it. That would be bad.
*/
error = RETURN_OK;
/* Open libraries */
SysBase = *((struct Library **) 4L);
if (DOSBase = OpenLibrary("dos.library", 37))
{
/* First, get the packet that the newshell segment sends. */
globals.mypacket = WaitPkt();
globals.myprocess = (struct Process *) FindTask(NULL);
/*
* Some arcane magic here for the UserShell. We have to look at this
* process' array of Segment pointers. If entry 4 in the array is NULL, we
* have to move entry 3 to entry 4 and NULL entry 4. This is because entry
* 3 will be used to store the seglist pointer for each program this shell
* runs.
*/
segment = (BPTR *) BADDR(globals.myprocess->pr_SegList);
if (!segment[4])
{
segment[4] = segment[3];
segment[3] = NULL;
}
/*
* The packet that newshell sends tells us how the shell was invoked. The
* dp_Res1 and dp_Res2 fields of the packet structure represent,
* respectively, the high order bit and low order bit of a two-bit
* bitfield. The following line of code will turn these values into a
* value from 0 to 3:
*/
shelltype = (globals.mypacket->dp_Res1 == 0 ? 0 : 2) |
(globals.mypacket->dp_Res2 == 0 ? 0 : 1);
/*
* at the moment, only the values 0 and 2 are defined. Type 0 is for Run,
* Execute(), and System(). Type 2 is for NewShell and NewCli.
*/
if ((shelltype == 2) || (shelltype == 0))
{
/*
* These two functions CliInitNewcli() and CliInitRun() take care setting
* up the shell's CommandLineInterface structure (current directories,
* paths, input streams...) using the secret startup packet we got
* earlier. They differ slightly in their setup based on the shell type.
* The exact workings of these functions is private and personal to DOS,
* and is subject to change. If you are wondering what exactly these
* functions do, don't worry about it. That would also be bad.
*/
if (shelltype == 0)
globals.fn = CliInitRun(globals.mypacket);
else
/*
* CliInitNewCli() handles the shell startup file (default is
* s:Shell-startup) and stuffs a filehandle to it into
* globals.mycli->cli_CurrentInput.
*/
globals.fn = CliInitNewcli(globals.mypacket);
/*
* Definitions for the values of globals.fn:
* Bit 31 Set to indicate flags are valid
* Bit 3 Set to indicate asynch system call
* Bit 2 Set if this is a System() call
* Bit 1 Set if user provided input stream
* Bit 0 Set if RUN provided output stream
*/
/*
* If the high bit of globals.fn is clear, check IoErr() to see if it
* points to this process. If it does, there was an error with the
* CliInitXxx... function. On an error, clean up and exit. You won't
* have to return the packet if there was an error because the
* CliInitXxxx function will take care of that.
*/
if ((globals.fn & 0x80000000) == 0) /* Is high bit clear? */
if ((struct Process *) IoErr() == globals.myprocess) /* is there an error? */
error = RETURN_FAIL;
else if (shelltype == 0)
{
ReplyPkt(globals.mypacket,
globals.mypacket->dp_Res1,
globals.mypacket->dp_Res2);
globals.mypacket = NULL;
}
if (error != RETURN_FAIL)
{
/*
* OK, no error. If this shell was invoked via NewShell or NewCLI
* (shelltype == 2), or if this is an asynchronous System() initiated
* shell, return the startup message. Although this example doesn't
* do it, if shelltype == 0, you can wait to reply the packet until you
* try to LoadSeg() your first command (to avoid disk gronking). When
* you use ReplyPkt() to reply the packet, use it like it appears below
* to avoid losing error codes set up by CliInitXxx.
*/
if (((globals.fn & 0x8000000C) == 0x8000000C) || (shelltype == 2))
{
ReplyPkt(globals.mypacket,
globals.mypacket->dp_Res1,
globals.mypacket->dp_Res2);
globals.mypacket = NULL;
}
if (globals.mycli = Cli())
{
/* Set up local shell variables and any custom set up here */
globals.mycli->cli_ReturnCode = 0;
globals.mycli->cli_Result2 = 0;
globals.myprocess->pr_HomeDir = NULL;
/* Ready to start processing commands */
error = mainshellloop(ml);
if (globals.fn < 0) /* if we got valid flags from
* CliInitXxxx (High bit of fn is set). */
{
Flush(Output());
/* if user DID NOT provide input stream, close standardinput */
if ((globals.fn & 2) == 0)
Close(globals.mycli->cli_StandardInput);
/* if RUN provided output stream, close it */
if ((globals.fn & 1) == 1)
{
Flush(globals.mycli->cli_StandardOutput);
Close(globals.mycli->cli_StandardOutput);
}
/* If we didn't send the packet back yet, send it back */
if (globals.mypacket)
ReplyPkt(globals.mypacket, error, globals.mypacket->dp_Res2);
}
else
/*
* the flags weren't valid so close the Standard I/O handles if
* they still exist.
*/
{
if (globals.mycli->cli_StandardOutput)
{
Flush(globals.mycli->cli_StandardOutput);
Close(globals.mycli->cli_StandardOutput);
}
if (globals.mycli->cli_StandardInput)
{
Flush(globals.mycli->cli_StandardInput);
Close(globals.mycli->cli_StandardInput);
}
}
/* release the process' lock on the current directory */
UnLock(globals.myprocess->pr_CurrentDir);
}
else
error = RETURN_FAIL; /* I have a NULL CLI! */
}
}
else
/* shelltype != 0 or 2 */
{
error = RETURN_FAIL;
ReplyPkt(globals.mypacket,
globals.mypacket->dp_Res1,
globals.mypacket->dp_Res2);
}
CloseLibrary(DOSBase);
}
else
error = RETURN_FAIL;
return error;
}
long mainshellloop(struct mylocals * ml)
{
BOOL done = FALSE;
unsigned char ch, *prompt, *command, *commandname, *cmd, *cmdname;
struct Segment *cmdseg;
long result;
WORD x;
ml->mycli->cli_FailLevel = RETURN_FAIL;
if (command = (char *) AllocVec(COMMANDLINELENGTH + COMMANDBUFLENGTH +
PROMPTLENGTH, MEMF_CLEAR))
{
commandname = &(command[COMMANDLINELENGTH]);
prompt = &(command[COMMANDLINELENGTH + COMMANDBUFLENGTH]);
do
{
/* Make sure the shell looks to cli_CurrentInput for its command lines */
SelectInput(ml->mycli->cli_CurrentInput);
/* is this an interactive shell? */
ml->mycli->cli_Interactive =
/* if this is not a backround CLI, and */
((!(ml->mycli->cli_Background)) &&
/* input has not been redirected to an script file, and */
NOTSCRIPT &&
/* this shell was not started from System() */
(!SYSTEM)) ? DOSTRUE : DOSFALSE;
/* if this is a script and the user hit CTRL-D, break out of the script */
if (!((SetSignal(0L, SIGBREAKF_CTRL_C |
SIGBREAKF_CTRL_D |
SIGBREAKF_CTRL_E |
SIGBREAKF_CTRL_F) & SIGBREAKF_CTRL_D) &&
(!SYSTEM) && (ISSCRIPT)))
{
/* if this shell is interactive and there is a prompt, print it */
/* (unless, of course, this was created by Run, etc) */
if (ml->mycli->cli_Interactive == DOSTRUE && !(ml->mycli->cli_Background))
{
/*
* If this wasn't an example, I would probably change the prompt
* here, probably to reflect the name of the current directory.
*/
/* print the prompt */
if (GetPrompt(prompt, 256))
{
FPuts(Output(), prompt);
/* Make sure the prompt gets printed */
Flush(Output());
}
}
/* Get Command */
if (FGets(ml->mycli->cli_CurrentInput, command, COMMANDLINELENGTH))
{
cmd = command;
/* skip leading spaces in command line */
while (*cmd == ' ')
cmd++;
/*
* If I was bothering to deal with aliases, I would probably resolve
* them here.
*/
cmdname = commandname;
x = 0;
/* copy the actual command from the cmd buffer */
while ((*cmd >= '0') && (*cmd <= 'z') && (x < (COMMANDBUFLENGTH - 1)))
{
*cmdname++ = *cmd++;
x++;
}
*cmdname = '\0';
/*
* OK, now we have the actual command in commandname. Using it we can
* find the actual executeable code. The command could come from
* several sources:
*
* The resident list
* The shell (an internal command)
* disk (from either an absolute or relative path)
*
* This example only looks through the resident list for commands. A
* real shell would also try to load a command from disk if the
* command is not present in the resident list (or the command is not
* internal to the shell.
*/
/* Search resident list for the command */
Forbid();
if (!(cmdseg = FindSegment(commandname, NULL, FALSE)))
cmdseg = FindSegment(commandname, NULL, TRUE);
if (cmdseg)
{
if ((cmdseg->seg_UC < CMD_DISABLED) ||
(cmdseg->seg_UC == CMD_SYSTEM))
cmdseg = NULL;
else if (cmdseg->seg_UC >= 0)
cmdseg->seg_UC++;
}
Permit();
/*
* if !cmdseg, the command was not in the resident list. If I were
* bothering to look for commands on disk, I would try to load the
* command here. If I has successfully loaded a command and was
* going to execute it, I would have to set ml->myprocess->pr_HomeDir
* to be a DupLock() of the directory I loaded the command from. I
* don't do this for commands from the resident list because they
* have no home directory.
*/
/* If we did find a command, run it */
if (cmdseg)
{
/* Clear the error field before executing the command */
SetIoErr(0);
SetProgramName(commandname);
ml->mycli->cli_Module = cmdseg->seg_Seg;
/*
* Set the I/O streams to their defaults. NOTE: StandardInput, NOT
* CurrentInput! The Execute command will cause nasty things to
* happen if you use CurrentInput, since it must close that in
* order to change the input stream to the next file. Obviously,
* this only applies if you're using the normal AmigaDOS Execute
* command for scripts.
*/
SelectInput(ml->mycli->cli_StandardInput);
SelectOutput(ml->mycli->cli_StandardOutput);
/*
* If I were doing redirection, the I/O handles above would be the
* redirection handles.
*/
/* Run the command */
result = RunCommand(ml->mycli->cli_Module,
(ml->mycli->cli_DefaultStack * 4),
cmd,
strlen(cmd));
/*
* OK, we returned from the command. Fill in any error codes in
* the appropriate CLI fields.
*/
ml->mycli->cli_ReturnCode = result;
ml->mycli->cli_Result2 = IoErr();
/* If I had bothered to load code from an executable file on disk,
* I would have to unload it now. Since I didn't, all I have to do
* is NULL cli_Module.
*/
ml->mycli->cli_Module = NULL;
SetProgramName("");
Forbid();
if (cmdseg->seg_UC > 0)
cmdseg->seg_UC--;
Permit();
cmdseg = NULL;
}
else
{
/* we couldn't find the command. Print an error message unless the
* command starts with a non-alphanumeric character (like a
* carriage return) or the first character is a comment character.
*/
if ((commandname[0] >= '0') &&
(commandname[0] <= 'z') &&
(commandname[0] != ';'))
{
PutStr(commandname);
PutStr(": Command not found\n");
Flush(Output());
}
}
/* if you set up redirection I/O handles for the command don't forget
* to flush and close them.
*/
/* Make sure the proper I/O handles are in place. */
SelectInput(ml->mycli->cli_CurrentInput);
SelectOutput(ml->mycli->cli_StandardOutput);
/* Get rid of any unused data left in the buffer */
ch = UnGetC(Input(), -1) ? '\0' : '\n';
while ((ch != '\n') && (ch != ENDSTREAMCH))
ch = FGetC(Input());
if (ch == ENDSTREAMCH)
done = TRUE;
}
else
done = TRUE; /* We got an EOF when reading in a
* command */
if (done)
{
if (ISSCRIPT)
{
done = FALSE; /* this is a script (which could be
* s:shell-startup), so don't quit, just
* exit the script and set up IO
* handles. */
/* Close the script file */
Close(ml->mycli->cli_CurrentInput);
/* Reset the input to what we started with */
SelectInput(ml->mycli->cli_StandardInput);
ml->mycli->cli_CurrentInput = ml->mycli->cli_StandardInput;
/* Restore Fail Level after executing a script */
ml->mycli->cli_FailLevel = RETURN_ERROR;
/* if the script created a file, delete it */
if (((char *) BADDR(ml->mycli->cli_CommandFile))[0])
{
cmd = (char *) BADDR(ml->mycli->cli_CommandFile);
CopyMem(&(cmd[1]), command, (LONG) cmd[0]);
command[cmd[0]] = '\0';
DeleteFile(command);
cmd[0] = '\0';
}
}
}
}
else
/* Somebody hit CTRL_D in a script */
{
/* print the string associated with error #304 */
PrintFault(304, "MyShell");
/* Close the script file */
Close(ml->mycli->cli_CurrentInput);
/* Reset the input to what we started with */
SelectInput(ml->mycli->cli_StandardInput);
ml->mycli->cli_CurrentInput = ml->mycli->cli_StandardInput;
cmd = (char *) BADDR(ml->mycli->cli_CommandFile);
cmd[0] = '\0';
}
/* this takes care of some problems certain programs caused */
if (SYSTEM && NOTSCRIPT)
done = TRUE;
} while (!done);
FreeVec((void *) command);
}
return result;
}
long strlen(UBYTE * string)
{
long x = 0L;
while (string[x]) x++;
return x;
}