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 >
C/C++ Source or Header  |  1996-01-30  |  19KB  |  527 lines

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