home *** CD-ROM | disk | FTP | other *** search
/ Amiga Developer CD 2.1 / Amiga Developer CD v2.1.iso / Reference / Amiga_Mail_Vol2 / Archives / Plain / jf92 / UserShell / myshell.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-01-03  |  20.3 KB  |  553 lines

  1. ;/* myshell.c - Execute to compile me with Lattice 5.10b using pragmas
  2. ; (c)  Copyright 1992 Commodore-Amiga, Inc.   All rights reserved.
  3. ; The information contained herein is subject to change without notice,
  4. ; and is provided "as is" without warranty of any kind, either expressed
  5. ; or implied.  The entire risk as to the use of this information is
  6. ; assumed by the user.
  7. ;
  8. LC -b0 -cfist -v -r1 -rr -d0 -j73 -dDOPRAGMAS myshell.c
  9. Blink FROM myshell.o TO myshell smallcode smalldata
  10. quit
  11.  
  12. Where:
  13.     -b0         means   Locate data as a 32 bit address.
  14.     -cfist      means   Compatibility options:
  15.                             f - Forces compiler to complain
  16.                                   if a function has no prototype.
  17.                             i - Ignores extra #includes of
  18.                                   an already #included file.
  19.                             s - Tells LC to generate a single copy
  20.                                   of all identical string constants
  21.                                   into a program's code section.
  22.                             t - Tells LC to warn you if it encounters
  23.                                   a structure or union tag that has
  24.                                   not been defined.
  25.     -v          means   Disable the stack checking LC normally
  26.                             puts at the beginning of each function.
  27.     -r1         means   Make all subroutines "near".  The compiler will
  28.                             use a 16-bit PC relative address to locate
  29.                             the subroutine.
  30.     -rr         means   Use registerized parameters for subroutines (no
  31.                             amiga.lib stubs!).
  32.     -d0         means   Do not put any debugging info into the object file.
  33.     -j73        means   Ignore warning #73.  This basically makes LC
  34.                             ignore the script comment delimiter (the
  35.                             semicolon) at the front of this file.
  36.                             amiga.lib stubs!).
  37. */
  38.  
  39. /*
  40.  * This is a basically a skeleton of a UserShell.  A UserShell is a special
  41.  * shell that can replace the default system shell.  It has to meets some
  42.  * system requirements to function as a system shell.  This example takes care
  43.  * of all of those requirements. To make this shell the system shell, use the
  44.  * resident command:
  45.  *
  46.  * resident shell MyShell SYSTEM
  47.  *
  48.  * Because this shell only serves as an minimal example of a UserShell and does
  49.  * not do many of the standard functions a shell normally performs.  It is
  50.  * limited to the commands from the resident list (which would make it a bad
  51.  * idea to add this shell to the resident list if you need a useable default
  52.  * shell!)
  53.  */
  54.  
  55. #include <exec/types.h>
  56. #include <exec/memory.h>
  57. #include <dos/dosextens.h>
  58. #include <dos/stdio.h>
  59. #include <clib/exec_protos.h>
  60. #include <clib/dos_protos.h>
  61. #include <clib/alib_stdio_protos.h>
  62.  
  63.  
  64. #ifdef DOPRAGMAS
  65. #include <pragmas/exec_pragmas.h>
  66. #include <pragmas/dos_pragmas.h>
  67. #endif
  68.  
  69. long            main(void);
  70.  
  71. #define COMMANDBUFLENGTH    64
  72. #define COMMANDLINELENGTH   512
  73. #define PROMPTLENGTH        256
  74.  
  75. /* True if this is a System() Instigated shell */
  76. #define SYSTEM ((ml->fn & 0x80000004) == 0x80000004)
  77.  
  78. /* true if this shell is executing a script */
  79. #define ISSCRIPT (ml->mycli->cli_CurrentInput != ml->mycli->cli_StandardInput)
  80.  
  81. /* true if this shell is *not* executing a script */
  82. #define NOTSCRIPT (ml->mycli->cli_CurrentInput == ml->mycli->cli_StandardInput)
  83.  
  84. struct mylocals
  85. {
  86.   struct Library    *sysBase;
  87.   struct Library    *dosBase;
  88.   struct Process    *myprocess;
  89.   struct DosPacket  *mypacket;
  90.   long              fn;        /* notice that fn is signed.  Some conditionals
  91.                                   in this code rely on this value being signed.
  92.                                */
  93.   struct CommandLineInterface *mycli;
  94. };
  95.  
  96. /*
  97.  * define the library base labels (SysBase and DOSBase)
  98.  * so we don't have to declare them as a global.  Can't have global data in
  99.  * resident code.
  100.  */
  101. #define SysBase (ml->sysBase)
  102. #define DOSBase (ml->dosBase)
  103.  
  104. long            mainshellloop(struct mylocals *);
  105. long            strlen(UBYTE *);
  106.  
  107. long
  108. main(void)
  109. {
  110.   struct mylocals globals, *ml = &globals;
  111.   BPTR           *segment;
  112.   long            shelltype, error;
  113.  
  114.   /*
  115.    * Poof, this shell has winked into existence.  It could have come from the
  116.    * user executing the "newshell" code via the newshell program, or it could
  117.    * have come from some other application using one of the DOS calls that use
  118.    * a shell, like System().  In any case, whatever caused this shell to wink
  119.    * into existence will also cause a special startup packet to appear in this
  120.    * process' message port.  This packet is very personal and private to DOS
  121.    * and probably will change with future versions of the OS, so don't worry
  122.    * about what's in it.  That would be bad.
  123.    */
  124.   error = RETURN_OK;
  125.   /* Open libraries */
  126.   SysBase = *((struct Library **) 4L);
  127.   if (DOSBase = OpenLibrary("dos.library", 37))
  128.   {
  129.     /* First, get the packet that the newshell segment sends. */
  130.     globals.mypacket = WaitPkt();
  131.     globals.myprocess = (struct Process *) FindTask(NULL);
  132.  
  133.     /*
  134.      * Some arcane magic here for the UserShell.  We have to look at this
  135.      * process' array of Segment pointers.  If entry 4 in the array is NULL, we
  136.      * have to move entry 3 to entry 4 and NULL entry 4.  This is because entry
  137.      * 3 will be used to store the seglist pointer for each program this shell
  138.      * runs.
  139.      */
  140.     segment = (BPTR *) BADDR(globals.myprocess->pr_SegList);
  141.     if (!segment[4])
  142.     {
  143.       segment[4] = segment[3];
  144.       segment[3] = NULL;
  145.     }
  146.     /*
  147.      * The packet that newshell sends tells us how the shell was invoked. The
  148.      * dp_Res1 and dp_Res2 fields of the packet structure represent,
  149.      * respectively, the high order bit and low order bit of a two-bit
  150.      * bitfield.  The following line of code will turn these values into a
  151.      * value from 0 to 3:
  152.      */
  153.     shelltype = (globals.mypacket->dp_Res1 == 0 ? 0 : 2) |
  154.       (globals.mypacket->dp_Res2 == 0 ? 0 : 1);
  155.  
  156.     /*
  157.      * at the moment, only the values 0 and 2 are defined.  Type 0 is for Run,
  158.      * Execute(), and System().  Type 2 is for NewShell and NewCli.
  159.      */
  160.     if ((shelltype == 2) || (shelltype == 0))
  161.     {
  162.  
  163.       /*
  164.        * These two functions CliInitNewcli() and CliInitRun() take care setting
  165.        * up the shell's CommandLineInterface structure (current directories,
  166.        * paths, input streams...) using the secret startup packet we got
  167.        * earlier.  They differ slightly in their setup based on the shell type.
  168.        * The exact workings of these functions is private and personal to DOS,
  169.        * and is subject to change. If you are wondering what exactly these
  170.        * functions do, don't worry about it.  That would also be bad.
  171.        */
  172.       if (shelltype == 0)
  173.         globals.fn = CliInitRun(globals.mypacket);
  174.       else
  175.  
  176.         /*
  177.          * CliInitNewCli() handles the shell startup file (default is
  178.          * s:Shell-startup) and stuffs a filehandle to it into
  179.          * globals.mycli->cli_CurrentInput.
  180.          */
  181.         globals.fn = CliInitNewcli(globals.mypacket);
  182.  
  183.       /*
  184.        * Definitions for the values of globals.fn:
  185.        *     Bit 31     Set to indicate flags are valid
  186.        *     Bit  3     Set to indicate asynch system call
  187.        *     Bit  2     Set if this is a System() call
  188.        *     Bit  1     Set if user provided input stream
  189.        *     Bit  0     Set if RUN provided output stream
  190.        */
  191.  
  192.       /*
  193.        * If the high bit of globals.fn is clear, check IoErr() to see if it
  194.        * points to this process.  If it does, there was an error with the
  195.        * CliInitXxx... function.  On an error, clean up and exit.  You won't
  196.        * have to return the packet if there was an error because the
  197.        * CliInitXxxx function will take care of that.
  198.        */
  199.       if ((globals.fn & 0x80000000) == 0)       /* Is high bit clear? */
  200.         if ((struct Process *) IoErr() == globals.myprocess)   /* is there an error? */
  201.           error = RETURN_FAIL;
  202.         else if (shelltype == 0)
  203.         {
  204.           ReplyPkt(globals.mypacket,
  205.                    globals.mypacket->dp_Res1,
  206.                    globals.mypacket->dp_Res2);
  207.           globals.mypacket = NULL;
  208.         }
  209.       if (error != RETURN_FAIL)
  210.       {
  211.  
  212.         /*
  213.          * OK, no error.  If this shell was invoked via NewShell or NewCLI
  214.          * (shelltype == 2), or if this is an asynchronous System() initiated
  215.          * shell, return the startup message.   Although this example doesn't
  216.          * do it, if shelltype == 0, you can wait to reply the packet until you
  217.          * try to LoadSeg() your first command (to avoid disk gronking). When
  218.          * you use ReplyPkt() to reply the packet, use it like it appears below
  219.          * to avoid losing error codes set up by CliInitXxx.
  220.          */
  221.         if (((globals.fn & 0x8000000C) == 0x8000000C) || (shelltype == 2))
  222.         {
  223.           ReplyPkt(globals.mypacket,
  224.                    globals.mypacket->dp_Res1,
  225.                    globals.mypacket->dp_Res2);
  226.           globals.mypacket = NULL;
  227.         }
  228.  
  229.         if (globals.mycli = Cli())
  230.         {
  231.           /* Set up local shell variables and any custom set up here */
  232.           globals.mycli->cli_ReturnCode = 0;
  233.           globals.mycli->cli_Result2 = 0;
  234.           globals.myprocess->pr_HomeDir = NULL;
  235.  
  236.           /* Ready to start processing commands */
  237.           error = mainshellloop(ml);
  238.           if (globals.fn < 0)          /* if we got valid flags from
  239.                                         * CliInitXxxx (High bit of fn is set). */
  240.           {
  241.             Flush(Output());
  242.             /* if user DID NOT provide input stream, close standardinput */
  243.             if ((globals.fn & 2) == 0)
  244.               Close(globals.mycli->cli_StandardInput);
  245.  
  246.             /* if RUN provided output stream, close it */
  247.             if ((globals.fn & 1) == 1)
  248.             {
  249.               Flush(globals.mycli->cli_StandardOutput);
  250.               Close(globals.mycli->cli_StandardOutput);
  251.             }
  252.  
  253.             /* If we didn't send the packet back yet, send it back */
  254.             if (globals.mypacket)
  255.               ReplyPkt(globals.mypacket, error, globals.mypacket->dp_Res2);
  256.           }
  257.           else
  258.             /*
  259.              * the flags weren't valid so close the Standard I/O handles if
  260.              * they still exist.
  261.              */
  262.           {
  263.             if (globals.mycli->cli_StandardOutput)
  264.             {
  265.               Flush(globals.mycli->cli_StandardOutput);
  266.               Close(globals.mycli->cli_StandardOutput);
  267.             }
  268.             if (globals.mycli->cli_StandardInput)
  269.             {
  270.               Flush(globals.mycli->cli_StandardInput);
  271.               Close(globals.mycli->cli_StandardInput);
  272.             }
  273.           }
  274.           /* release the process' lock on the current directory */
  275.           UnLock(globals.myprocess->pr_CurrentDir);
  276.         }
  277.         else
  278.           error = RETURN_FAIL;         /* I have a NULL CLI! */
  279.       }
  280.     }
  281.     else
  282.       /* shelltype != 0 or 2 */
  283.     {
  284.       error = RETURN_FAIL;
  285.       ReplyPkt(globals.mypacket,
  286.                globals.mypacket->dp_Res1,
  287.                globals.mypacket->dp_Res2);
  288.     }
  289.     CloseLibrary(DOSBase);
  290.   }
  291.   else
  292.     error = RETURN_FAIL;
  293.  
  294.   return error;
  295. }
  296. long mainshellloop(struct mylocals * ml)
  297. {
  298.   BOOL            done = FALSE;
  299.   unsigned char   ch, *prompt, *command, *commandname, *cmd, *cmdname;
  300.   struct Segment *cmdseg;
  301.   long            result;
  302.   WORD            x;
  303.  
  304.   ml->mycli->cli_FailLevel = RETURN_FAIL;
  305.  
  306.   if (command = (char *) AllocVec(COMMANDLINELENGTH + COMMANDBUFLENGTH +
  307.                                   PROMPTLENGTH, MEMF_CLEAR))
  308.   {
  309.     commandname = &(command[COMMANDLINELENGTH]);
  310.     prompt = &(command[COMMANDLINELENGTH + COMMANDBUFLENGTH]);
  311.     do
  312.     {
  313.       /* Make sure the shell looks to cli_CurrentInput for its command lines */
  314.  
  315.       SelectInput(ml->mycli->cli_CurrentInput);
  316.       /* is this an interactive shell? */
  317.       ml->mycli->cli_Interactive =
  318.       /* if this is not a backround CLI, and */
  319.         ((!(ml->mycli->cli_Background)) &&
  320.       /* input has not been redirected to an script file, and */
  321.          NOTSCRIPT &&
  322.       /* this shell was not started from System() */
  323.          (!SYSTEM)) ? DOSTRUE : DOSFALSE;
  324.  
  325.       /* if this is a script and the user hit CTRL-D, break out of the script */
  326.       if (!((SetSignal(0L, SIGBREAKF_CTRL_C |
  327.                        SIGBREAKF_CTRL_D |
  328.                        SIGBREAKF_CTRL_E |
  329.                        SIGBREAKF_CTRL_F) & SIGBREAKF_CTRL_D) &&
  330.             (!SYSTEM) && (ISSCRIPT)))
  331.       {
  332.         /* if this shell is interactive and there is a prompt, print it */
  333.         /* (unless, of course, this was created by Run, etc) */
  334.         if (ml->mycli->cli_Interactive == DOSTRUE && !(ml->mycli->cli_Background))
  335.         {
  336.  
  337.           /*
  338.            * If this wasn't an example, I would probably change the prompt
  339.            * here, probably to reflect the name of the current directory.
  340.            */
  341.           /* print the prompt */
  342.           if (GetPrompt(prompt, 256))
  343.           {
  344.             FPuts(Output(), prompt);
  345.             /* Make sure the prompt gets printed */
  346.             Flush(Output());
  347.           }
  348.         }
  349.         /* Get Command */
  350.         if (FGets(ml->mycli->cli_CurrentInput, command, COMMANDLINELENGTH))
  351.         {
  352.           cmd = command;
  353.           /* skip leading spaces in command line */
  354.           while (*cmd == ' ')
  355.             cmd++;
  356.  
  357.           /*
  358.            * If I was bothering to deal with aliases, I would probably resolve
  359.            * them here.
  360.            */
  361.  
  362.           cmdname = commandname;
  363.           x = 0;
  364.           /* copy the actual command from the cmd buffer */
  365.           while ((*cmd >= '0') && (*cmd <= 'z') && (x < (COMMANDBUFLENGTH - 1)))
  366.           {
  367.             *cmdname++ = *cmd++;
  368.             x++;
  369.           }
  370.           *cmdname = '\0';
  371.           /*
  372.            * OK, now we have the actual command in commandname. Using it we can
  373.            * find the actual executeable code.  The command could come from
  374.            * several sources:
  375.            *
  376.            * The resident list
  377.            * The shell (an internal command)
  378.            * disk (from either an absolute or relative path)
  379.            *
  380.            * This example only looks through the resident list for commands. A
  381.            * real shell would also try to load a command from disk if the
  382.            * command is not present in the resident list (or the command is not
  383.            * internal to the shell.
  384.            */
  385.  
  386.           /* Search resident list for the command */
  387.           Forbid();
  388.           if (!(cmdseg = FindSegment(commandname, NULL, FALSE)))
  389.             cmdseg = FindSegment(commandname, NULL, TRUE);
  390.           if (cmdseg)
  391.           {
  392.             if ((cmdseg->seg_UC < CMD_DISABLED) ||
  393.                 (cmdseg->seg_UC == CMD_SYSTEM))
  394.               cmdseg = NULL;
  395.             else if (cmdseg->seg_UC >= 0)
  396.               cmdseg->seg_UC++;
  397.           }
  398.           Permit();
  399.  
  400.           /*
  401.            * if !cmdseg, the command was not in the resident list.  If I were
  402.            * bothering to look for commands on disk, I would try to load the
  403.            * command here.  If I has successfully loaded a command and was
  404.            * going to execute it, I would have to set ml->myprocess->pr_HomeDir
  405.            * to be a DupLock() of the directory I loaded the command from.  I
  406.            * don't do this for commands from the resident list because they
  407.            * have no home directory.
  408.            */
  409.  
  410.           /* If we did find a command, run it */
  411.           if (cmdseg)
  412.           {
  413.             /* Clear the error field before executing the command */
  414.             SetIoErr(0);
  415.  
  416.             SetProgramName(commandname);
  417.             ml->mycli->cli_Module = cmdseg->seg_Seg;
  418.  
  419.             /*
  420.              * Set the I/O streams to their defaults. NOTE: StandardInput, NOT
  421.              * CurrentInput!  The Execute command will cause nasty things to
  422.              * happen if you use CurrentInput, since it must close that in
  423.              * order to change the input stream to the next file. Obviously,
  424.              * this only applies if you're using the normal AmigaDOS Execute
  425.              * command for scripts.
  426.              */
  427.             SelectInput(ml->mycli->cli_StandardInput);
  428.             SelectOutput(ml->mycli->cli_StandardOutput);
  429.  
  430.             /*
  431.              * If I were doing redirection, the I/O handles above would be the
  432.              * redirection handles.
  433.              */
  434.  
  435.             /* Run the command */
  436.             result = RunCommand(ml->mycli->cli_Module,
  437.                                 (ml->mycli->cli_DefaultStack * 4),
  438.                                 cmd,
  439.                                 strlen(cmd));
  440.             /*
  441.              * OK, we returned from the command.  Fill in any error codes in
  442.              * the appropriate CLI fields.
  443.              */
  444.             ml->mycli->cli_ReturnCode = result;
  445.             ml->mycli->cli_Result2 = IoErr();
  446.             /* If I had bothered to load code from an executable file on disk,
  447.              * I would have to unload it now.  Since I didn't, all I have to do
  448.              * is NULL cli_Module.
  449.              */
  450.             ml->mycli->cli_Module = NULL;
  451.  
  452.             SetProgramName("");
  453.             Forbid();
  454.             if (cmdseg->seg_UC > 0)
  455.               cmdseg->seg_UC--;
  456.             Permit();
  457.             cmdseg = NULL;
  458.           }
  459.           else
  460.           {
  461.             /* we couldn't find the command.  Print an error message unless the
  462.              * command starts with a non-alphanumeric character (like a
  463.              * carriage return) or the first character is a comment character.
  464.              */
  465.             if ((commandname[0] >= '0') &&
  466.                 (commandname[0] <= 'z') &&
  467.                 (commandname[0] != ';'))
  468.             {
  469.               PutStr(commandname);
  470.               PutStr(": Command not found\n");
  471.               Flush(Output());
  472.             }
  473.           }
  474.  
  475.           /* if you set up redirection I/O handles for the command don't forget
  476.            * to flush and close them.
  477.            */
  478.  
  479.           /* Make sure the proper I/O handles are in place. */
  480.           SelectInput(ml->mycli->cli_CurrentInput);
  481.           SelectOutput(ml->mycli->cli_StandardOutput);
  482.  
  483.           /* Get rid of any unused data left in the buffer */
  484.           ch = UnGetC(Input(), -1) ? '\0' : '\n';
  485.           while ((ch != '\n') && (ch != ENDSTREAMCH))
  486.             ch = FGetC(Input());
  487.           if (ch == ENDSTREAMCH)
  488.             done = TRUE;
  489.         }
  490.         else
  491.           done = TRUE;                 /* We got an EOF when reading in a
  492.                                         * command */
  493.         if (done)
  494.         {
  495.           if (ISSCRIPT)
  496.           {
  497.             done = FALSE;              /* this is a script (which could be
  498.                                         * s:shell-startup), so don't quit, just
  499.                                         * exit the script and set up IO
  500.                                         * handles. */
  501.             /* Close the script file */
  502.             Close(ml->mycli->cli_CurrentInput);
  503.             /* Reset the input to what we started with */
  504.             SelectInput(ml->mycli->cli_StandardInput);
  505.             ml->mycli->cli_CurrentInput = ml->mycli->cli_StandardInput;
  506.  
  507.             /* Restore Fail Level after executing a script */
  508.             ml->mycli->cli_FailLevel = RETURN_ERROR;
  509.  
  510.             /* if the script created a file, delete it */
  511.             if (((char *) BADDR(ml->mycli->cli_CommandFile))[0])
  512.             {
  513.               cmd = (char *) BADDR(ml->mycli->cli_CommandFile);
  514.               CopyMem(&(cmd[1]), command, (LONG) cmd[0]);
  515.               command[cmd[0]] = '\0';
  516.               DeleteFile(command);
  517.               cmd[0] = '\0';
  518.             }
  519.           }
  520.         }
  521.       }
  522.       else
  523.         /* Somebody hit CTRL_D in a script */
  524.       {
  525.         /* print the string associated with error #304 */
  526.         PrintFault(304, "MyShell");
  527.         /* Close the script file */
  528.         Close(ml->mycli->cli_CurrentInput);
  529.         /* Reset the input to what we started with */
  530.         SelectInput(ml->mycli->cli_StandardInput);
  531.         ml->mycli->cli_CurrentInput = ml->mycli->cli_StandardInput;
  532.  
  533.         cmd = (char *) BADDR(ml->mycli->cli_CommandFile);
  534.         cmd[0] = '\0';
  535.       }
  536.       /* this takes care of some problems certain programs caused */
  537.       if (SYSTEM && NOTSCRIPT)
  538.         done = TRUE;
  539.  
  540.     } while (!done);
  541.     FreeVec((void *) command);
  542.   }
  543.   return result;
  544. }
  545.  
  546. long strlen(UBYTE * string)
  547. {
  548.   long            x = 0L;
  549.  
  550.   while (string[x]) x++;
  551.   return x;
  552. }
  553.