[Contents] [Index] [Help] [Retrace] [Browse <] [Browse >]

;/* 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;
}