;/* myshell.c - Execute to compile me with SAS/C 6.56 using pragmas ; (c) Copyright 1992-1999 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; }