Subversion Repositories DevTools

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * MS-DOS SHELL - Parse Tree Executor
 *
 * MS-DOS SHELL - Copyright (c) 1990,4 Data Logic Limited and Ch/arles Forsyth
 *
 * This code is based on (in part) the shell program written by Charles
 * Forsyth and is subject to the following copyright restrictions:
 *
 * 1.  Redistribution and use in source and binary forms are permitted
 *     provided that the above copyright notice is duplicated in the
 *     source form and the copyright notice in file sh6.c is displayed
 *     on entry to the program.
 *
 * 2.  The sources (or parts thereof) or objects generated from the sources
 *     (or parts of sources) cannot be sold under any circumstances.
 *
 * When parts of the original 2.1 shell were replaced by the Lexical
 * Analsyer written by Simon J. Gerraty (for his Public Domain Korn Shell,
 * which is also based on Charles Forsyth original idea), a number of changes
 * were made to reflect the changes Simon made to the Parse output tree.  Some
 * parts of this code in this module are based on the algorithms/ideas that
 * he incorporated into his shell, in particular the TCASE and TTIME
 * functionality in ExecuteParseTree, and the PrintAList function.
 *
 *    $Header: /cvsroot/device/DEVL/UTILS/SH/Sh3.c,v 1.3 2004/11/04 07:10:08 ayoung Exp $
 *
 *    $Log: Sh3.c,v $
 *    Revision 1.3  2004/11/04 07:10:08  ayoung
 *    additional EXEC debug, ProcessSpaceInParamaters for generated indirect files
 *
 *    Revision 1.2  2004/05/10 09:30:07  ayoung
 *    improved Path/PATH handling
 *    Quote CreateProcess arg0 if embedded spaces are  encountered
 *    Native NT exec dont need to check command line length
 *    Warning when '@' within redirect list
 *    DEBUG_EXEC option, split from PRINT_EXEC option and improved
 *
 *    Revision 1.1  2002/08/02 06:49:34  adamy
 *    imported (reference only)
 *
 *    Revision 1.3  2002/02/05 05:18:33  ayoung
 *    removed functionality that cause drive scanning during application execution
 *
 *    Revision 1.2  2001/08/24 06:02:05  ayoung
 *    WIN32 'sh' return code processing corrected, which previously always returned 0.
 *
 *    Revision 1.1  2001/07/20 05:55:44  ayoung
 *    WIN32 support
 *
 *    Revision 1.3  2000/10/05 09:26:53  adamy
 *    no message
 *
 *    Revision 1.2  2000/09/27 08:33:35  adamy
 *    Added EXTENDED_LINE cache and use __systeml_mode during DOS builds.
 *
 *    Revision 1.1.1.1  1999/12/02 01:11:12  gordonh
 *    UTIL
 *
 *      Revision 2.17  1994/08/25  20:49:11  istewart
 *      MS Shell 2.3 Release
 *
 *      Revision 2.16  1994/02/23  09:23:38  istewart
 *      Beta 233 updates
 *
 *      Revision 2.15  1994/02/01  10:25:20  istewart
 *      Release 2.3 Beta 2, including first NT port
 *
 *      Revision 2.14  1994/01/20  14:51:43  istewart
 *      Release 2.3 Beta 1
 *
 *      Revision 2.13  1994/01/11  17:55:25  istewart
 *      Release 2.3 Beta 0 patches
 *
 *      Revision 2.12  1993/11/09  10:39:49  istewart
 *      Beta 226 checking
 *
 *      Revision 2.11  1993/08/25  16:03:57  istewart
 *      Beta 225 - see Notes file
 *
 *      Revision 2.10  1993/07/02  10:21:35  istewart
 *      224 Beta fixes
 *
 *      Revision 2.9  1993/06/14  11:00:12  istewart
 *      More changes for 223 beta
 *
 *      Revision 2.8  1993/06/02  09:52:35  istewart
 *      Beta 223 Updates - see Notes file
 *
 *      Revision 2.7  1993/02/16  16:03:15  istewart
 *      Beta 2.22 Release
 *
 *      Revision 2.6  1993/01/26  18:35:09  istewart
 *      Release 2.2 beta 0
 *
 *      Revision 2.5  1992/12/14  10:54:56  istewart
 *      BETA 215 Fixes and 2.1 Release
 *
 *      Revision 2.4  1992/11/06  10:03:44  istewart
 *      214 Beta test updates
 *
 *      Revision 2.3  1992/09/03  18:54:45  istewart
 *      Beta 213 Updates
 *
 *      Revision 2.2  1992/07/16  14:33:34  istewart
 *      Beta 212 Baseline
 *
 *      Revision 2.1  1992/07/10  10:52:48  istewart
 *      211 Beta updates
 *
 *      Revision 2.0  1992/05/07  20:31:39  Ian_Stewartson
 *      MS-Shell 2.0 Baseline release
 *
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <signal.h>
#include <errno.h>
#include <setjmp.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>
#include <dirent.h>
#include <ctype.h>
#include <time.h>
#include "sh.h"
#if (OS_TYPE == OS_DOS)
#define SYSTEML_MODE
#include "libdos.h"
#endif

/*
 * Save struct for Parameters ($1, $2 etc)
 */
typedef struct SaveParameters {
    char        **Array;                /* The parameters               */
    int         Count;                  /* Number of them               */
} SaveParameters;

/* static Function and string declarations */

static int F_LOCAL      ForkAndExecute (C_Op *, int, int, int, char **, char **,
                                        char **);
static bool F_LOCAL     SetUpIOHandlers (IO_Actions *, int, int);
static bool F_LOCAL     WriteToExtendedFile (FILE *, char *);
static void F_LOCAL     EchoCurrentCommand (char **);
static int F_LOCAL      ExecuteProgram (char *, char **, char **, int);
static void F_LOCAL     SaveNumericParameters (char **, SaveParameters *);
static void F_LOCAL     RestoreTheParameters (SaveParameters *);
static bool F_LOCAL     ExecuteFunction (char **, int *, bool);
static void F_LOCAL     PrintLoadError (char *);
static void F_LOCAL     TrackAllCommands (char *, char *);
static int F_LOCAL      ExtensionType (char *);
static char * F_LOCAL   FindFileAndExtension (char *, char *, char **);
static bool F_LOCAL     GetApplicationType (char *);
static bool F_LOCAL     BadApplication (char *);
static bool F_LOCAL     SetUpCLI (char *, Word_B **);

static char * F_LOCAL   ConvertErrorNumber (void);
static int F_LOCAL      BuildCommandLine (char *, char **, char **, int);
static int F_LOCAL      StartTheProcess (char *, char **, char **, int);
static int F_LOCAL      SetCommandReturnStatus (int);
static char ** F_LOCAL  FindNumberOfValues (char **, int *);
static int F_LOCAL      ExecuteScriptFile (char *, char **, char **, int, bool);
#if (OS_TYPE != OS_UNIX)
static int F_LOCAL      ExecuteWindows (char *, char **, char **, int);
#endif
static int F_LOCAL      ExecuteSpecialProcessor (char *, char **, char **, int,
                                                 Word_B *);
static int F_LOCAL      EnvironExecute (char **, int);
static bool F_LOCAL     CheckParameterLength (char **);
static int F_LOCAL      LocalExecve (char **, char **, int);
static unsigned int F_LOCAL CheckForCommonOptions (LineFields *, int);
static char ** F_LOCAL  ProcessSpaceInParameters (char **);
static int F_LOCAL      CountDoubleQuotes (char *);
static void             BuildEnvironmentEntry (const void *, VISIT, int);
static char ** F_LOCAL  BuildCommandEnvironment (void);

#if (OS_TYPE != OS_DOS)
static void F_LOCAL     PrintPidStarted (void);

#  if (OS_TYPE == OS_OS2)
static char             *InsertCharacterAtStart (char *);
static int F_LOCAL      OS_DosExecProgram (int, char *, char **, char **);
#  elif (OS_TYPE == OS_NT)
static int F_LOCAL      OS_DosExecProgram (int, char *, char **, char **);
#  endif
#else
#  define PrintPidStarted()
#  define InsertCharacterAtStart(a)
#endif

static void              PrintProgramMode (ExeMode *PMode);

#if (OS_TYPE == OS_OS2)
static int F_LOCAL      StartTheSession (STARTDATA *, char *, char **,
                                         char **, int);
#endif

static char             *AE2big = "arg/env list too big (sh)";
                        /* Extended Command line processing file name   */
static char             *Extend_file = (char *)NULL;
static char             *DoubleQuotes = "\"";
static char             *WildCards = "*?[\"'";
static unsigned long    ApplicationType;

#if (OS_TYPE == OS_DOS)
static char             *LIT_STARTWINP = "STARTWINP";
#endif

/*
 * Global variables for TWALK to build command environment
 */

static Word_B   *BCE_WordList;
static int      BCE_Length;

/*
 * List of Executable (shell script, .exe, .com) extensions and list
 * including functions.
 */

static char     **ExecutableList = (char **)NULL;
static char     **ExecuteFunctionList = (char **)NULL;

/*
 * OS2 load error mode
 */

#if (OS_TYPE == OS_OS2)
static char             FailName[FFNAME_MAX];
#endif

#if (OS_TYPE != OS_DOS)
static OSCALL_RET       OS_DosExecPgmReturnCode;
#endif

/*
 * Program started info
 */

#if (OS_TYPE != OS_DOS)
struct PidInfo {
    bool        Valid;
    int         JobNo;
    int         PidNo;
} PidInfo;
#endif

/*
 * execute tree recursively
 */

int
ExecuteParseTree(
    C_Op *t, int StandardIN, int StandardOUT, int Actions )
{
    int                 Count;
    int                 LocalPipeFP;    /* Pipe handlers                */

#if (OS_TYPE != OS_DOS)
    int                 ReadPipeFP;
    int                 WritePipeFP;
#  if OS_TYPE == OS_NT
    HANDLE              ReadPipeH;
    HANDLE              WritePipeH;
#  endif
#endif

    char                *cp;
    char                **AList;        /* Argument list                */
    Break_C             BreakContinue;
                                        /* Save longjmp returns         */
    Break_C             *S_RList = Return_List;
    Break_C             *S_BList = Break_List;
    Break_C             *S_SList = SShell_List;

    GetoptsIndex        GetoptsSave;
    int                 Local_depth;    /* Save local values            */
    int                 Local_MemoryAreaLevel;
    int                 RetVal = 0;     /* Return value                 */
    char                *InputBuffer;   /* Select input Buffer          */
    char                *EndIB;         /* End of buffer                */
    char                *LastWord = null;

/* End of tree ? */

    if (t == (C_Op *)NULL)
        return SetCommandReturnStatus (0);

    DPRINT (1, ("ExecuteParseTree: t->type = %d, Depth = %d",
                t->type, Execute_stack_depth));

/* Save original and Increment execute function recursive level */

    Local_depth = Execute_stack_depth++;

/* Save original and increment area number */

    Local_MemoryAreaLevel = MemoryAreaLevel++;

/* Switch on tree node type */

    switch (t->type)
    {
        case TFUNC:                     /* name () { list; }    */
            RetVal = SaveFunction (t) ? 0 : 1;
            SetCommandReturnStatus (RetVal);
            break;

/* In the case of a () command string, we need to save and restore the
 * current environment, directory and traps (can't think of anything else).
 * For any other, we just restore the current directory.  Also, we don't
 * want changes in the Variable list header saved for SubShells, because
 * we are effectively back at execute depth zero.
 */
        case TPAREN:                    /* ()                   */
            if ((RetVal = CreateGlobalVariableList (FLAGS_NONE)) == -1)
                break;

/* Save Getopts pointers */

            GetGetoptsValues (&GetoptsSave);

            if (setjmp (BreakContinue.CurrentReturnPoint) == 0)
            {
                Return_List = (Break_C *)NULL;
                Break_List  = (Break_C *)NULL;
                BreakContinue.NextExitLevel  = SShell_List;
                SShell_List = &BreakContinue;
                RetVal = ForkAndExecute (t, StandardIN, StandardOUT, Actions,
                                         NOWORDS, NOWORDS, &LastWord);
            }

/* Restore the original environment */

            else
                RetVal = (int)GetVariableAsNumeric (StatusVariable);

            SaveGetoptsValues (GetoptsSave.Index, GetoptsSave.SubIndex);
            Return_List = S_RList;
            Break_List  = S_BList;
            SShell_List = S_SList;
            RestoreEnvironment (RetVal, Local_depth);
            break;

/* After a normal command, we need to restore the original directory.  Note
 * that a cd will have updated the variable $~, so no problem
 */

        case TCOM:                      /* A command process    */
        {
            ExeMode     SaveValues;
            char        **SetVlist;     /* Set variable list            */

            SetVlist = ExpandWordList (t->vars, EXPAND_TILDE, (ExeMode *)NULL);
            AList = ExpandWordList (t->args, EXPAND_SPLITIFS | EXPAND_GLOBBING |
                                    EXPAND_TILDE, &SaveValues);

            ExecProcessingMode = SaveValues;

#if (OS_TYPE != OS_DOS)
            PidInfo.Valid = FALSE;
#endif

            RetVal = ForkAndExecute (t, StandardIN, StandardOUT, Actions,
                                     AList, SetVlist, &LastWord);

            PrintPidStarted ();
            RestoreEnvironment (RetVal, Local_depth);

/* Save last word if appropriate */

            if (!(DisabledVariables & DISABLE_LASTWORD))
            {
                SetVariableFromString (LastWordVariable, LastWord);
                SetVariableStatus (LastWordVariable, STATUS_EXPORT);
            }
            break;
        }

        case TTIME:                     /* Time a command process       */
        {
            clock_t     stime;
            clock_t     etime;
            clock_t     dif;

            stime = clock ();
            RetVal = ExecuteParseTree (t->left, StandardIN, StandardOUT, 0);
            etime = clock ();

            feputc (CHAR_NEW_LINE);

            if ((dif = (etime - stime) / (60L * (clock_t)CLOCKS_PER_SEC)) != 0)
                fprintf (stderr, "%ldm ", dif);

            dif = (etime - stime) % (60L * (clock_t)CLOCKS_PER_SEC);

            fprintf (stderr, "%ld.%.3lds real\n",
                     dif / (clock_t)CLOCKS_PER_SEC,
                     dif % (clock_t)CLOCKS_PER_SEC);

            break;
        }

        case TPIPE:                     /* Pipe processing              */

            Actions |= EXEC_PIPE_IN;

#if (OS_TYPE == OS_UNIX)
            fputs ("UNIX: Pipes not implemented\n", stderr);
            RetVal = -1;

#else
#  if (OS_TYPE != OS_DOS)
/* Do we want to use real pipes under OS2? */

            if (ShellGlobalFlags & FLAGS_REALPIPES)
            {
#    if (OS_TYPE == OS_OS2)
#      if (OS_SIZE == OS_32)
                if (DosCreatePipe ((PHFILE) &ReadPipeFP,
                                   (PHFILE) &WritePipeFP, 4096))
                    break;
#      else
                if (DosMakePipe ((PHFILE) &ReadPipeFP,
                                 (PHFILE) &WritePipeFP, 0))
                    break;
#      endif

/* Remap the IO handler */

                ReadPipeFP = ReMapIOHandler (ReadPipeFP);
                WritePipeFP = ReMapIOHandler (WritePipeFP);
                DosSetFHandState (ReadPipeFP, OPEN_FLAGS_NOINHERIT);
                DosSetFHandState (WritePipeFP, OPEN_FLAGS_NOINHERIT);

#    elif (OS_TYPE == OS_NT)
                if (!CreatePipe (&ReadPipeH, &WritePipeH, 0, 0))
                    break;

/* Remap the IO handler */

                ReadPipeFP = _open_osfhandle ((long)ReadPipeH, _O_RDONLY);
                WritePipeFP = _open_osfhandle ((long)WritePipeH, _O_APPEND);
                ReadPipeFP = ReMapIOHandler (ReadPipeFP);
                WritePipeFP = ReMapIOHandler (WritePipeFP);
#    endif


/* Is this a foreground thingy? */

                if (!(Actions & (EXEC_SPAWN_NOWAIT | EXEC_SPAWN_IGNOREWAIT)))
                {
                    int WaitPid;

                    WaitPid = ExecuteParseTree (t->left, StandardIN,
                                                WritePipeFP,
                                                EXEC_SPAWN_IGNOREWAIT |
                                                (Actions & (EXEC_PIPE_IN |
                                                            EXEC_PIPE_SUBS)));
                    S_close (WritePipeFP, TRUE);

                    RetVal = ExecuteParseTree (t->right, ReadPipeFP,
                                               StandardOUT,
                                               Actions | EXEC_PIPE_SUBS);
                    S_close (ReadPipeFP, TRUE);
                    cwait (&WaitPid, WaitPid, WAIT_GRANDCHILD);
                }

/* Background processing */

                else
                {
                    ExecuteParseTree (t->left, StandardIN, WritePipeFP,
                                      EXEC_SPAWN_IGNOREWAIT |
                                      (Actions & (EXEC_PIPE_IN |
                                                  EXEC_PIPE_SUBS)));

                    S_close (WritePipeFP, TRUE);

                    RetVal = ExecuteParseTree (t->right, ReadPipeFP,
                                               StandardOUT,
                                               Actions | EXEC_PIPE_SUBS);
                    S_close (ReadPipeFP, TRUE);
                }

                break;
            }
#  endif

/* MSDOS or OS/2 without real pipes - use files.  Safer */

            if ((RetVal = OpenAPipe ()) < 0)
                break;

/* Create pipe, execute command, reset pipe, execute the other side, close
 * the pipe and fini
 */

            LocalPipeFP = ReMapIOHandler (RetVal);
            ExecuteParseTree (t->left, StandardIN, LocalPipeFP,
                              (Actions & (EXEC_PIPE_IN | EXEC_PIPE_SUBS)));

/* Close the Input to release the file descriptor */

            CloseThePipe (StandardIN);

            lseek (LocalPipeFP, 0L, SEEK_SET);
            RetVal = ExecuteParseTree (t->right, LocalPipeFP, StandardOUT,
                                       Actions | EXEC_PIPE_SUBS);
            CloseThePipe (LocalPipeFP);
#endif
            break;

        case TLIST:                     /* Entries in a for statement   */
            while (t->type == TLIST)
            {
                ExecuteParseTree (t->left, StandardIN, StandardOUT, 0);
                t = t->right;
            }

            RetVal = ExecuteParseTree (t, StandardIN, StandardOUT, 0);
            break;

        case TCOPROCESS:                /* Co processes                 */
            if (!FL_TEST (FLAG_WARNING))
                PrintWarningMessage ("sh: co-processes not supported");

            SetCommandReturnStatus (RetVal = -1);
            break;

        case TASYNC:                    /* Async - not supported        */
#if (OS_TYPE == OS_DOS)
            if (!FL_TEST (FLAG_WARNING))
                PrintWarningMessage ("sh: Async commands not supported");

            SetCommandReturnStatus (RetVal = -1);
#else
            RetVal = ExecuteParseTree (t->left, StandardIN, StandardOUT,
                                       EXEC_SPAWN_NOWAIT);
#endif
            break;

        case TOR:                       /* || and &&                    */
        case TAND:
            RetVal = ExecuteParseTree (t->left, StandardIN, StandardOUT, 0);

            if ((t->right != (C_Op *)NULL) &&
                ((RetVal == 0) == (t->type == TAND)))
                RetVal = ExecuteParseTree (t->right, StandardIN, StandardOUT,
                                           0);

            break;


/* for x do...done and for x in y do...done - find the start of the variables
 * count the number.
 */
        case TFOR:
        case TSELECT:
            AList = FindNumberOfValues (ExpandWordList (t->vars,
                                                        EXPAND_SPLITIFS |
                                                        EXPAND_GLOBBING |
                                                        EXPAND_TILDE,
                                                        (ExeMode *)NULL),
                                                        &Count);


/* Set up a long jump return point before executing the for function so that
 * the continue statement is executed, ie we reprocessor the for condition.
 */

            while ((RetVal = setjmp (BreakContinue.CurrentReturnPoint)) != 0)
            {

/* Restore the current stack level and clear out any I/O */

                RestoreEnvironment (0, Local_depth + 1);
                Return_List = S_RList;
                SShell_List = S_SList;

/* If this is a break - clear the variable and terminate the while loop and
 * switch statement
 */

                if (RetVal == BC_BREAK)
                    break;
            }

            if (RetVal == BC_BREAK)
                break;

/* Process the next entry - Add to the break/continue chain */

            BreakContinue.NextExitLevel = Break_List;
            Break_List = &BreakContinue;

/* Execute the command tree */

            if (t->type == TFOR)
            {
                while (Count--)
                {
                    SetVariableFromString (t->str, *AList++);
                    RetVal = ExecuteParseTree (t->left, StandardIN, StandardOUT,
                                               0);
                }
            }

/* Select option */

            else if (!Count)
            /* SKIP */;

/* Get some memory for the select input buffer */

            else if ((InputBuffer = AllocateMemoryCell (LINE_MAX))
                        == (char *)NULL)
            {
                ShellErrorMessage (Outofmemory1);
                RetVal = -1;
            }

/* Process the select command */

            else
            {
                bool    OutputList = TRUE;

                EndIB = &InputBuffer[LINE_MAX - 2];

                for (;;)
                {
                    int         ReadCount;      /* Local counter        */
                    int         OnlyDigits;     /* Only digits in string*/

/* Output list of words */

                    if (OutputList)
                    {
                        PrintAList (Count, AList);
                        OutputList = FALSE;
                    }

/* Output prompt */

                    OutputUserPrompt (PS3);
                    OnlyDigits = 1;

/* Read in until end of line, file or a field separator is detected */

                    for (cp = InputBuffer; (cp < EndIB); cp++)
                    {
                        if (((ReadCount = read (STDIN_FILENO, cp, 1)) != 1) ||
                            (*cp == CHAR_NEW_LINE))
                        {
                            break;
                        }

                        OnlyDigits = OnlyDigits && isdigit (*cp);
                    }

                    *cp = 0;

/* Check for end of file */

                    if (ReadCount != 1)
                        break;

/* Check for empty line */

                    if (!strlen (InputBuffer))
                    {
                        OutputList = TRUE;
                        continue;
                    }

                    SetVariableFromString (LIT_REPLY, InputBuffer);

/* Check that OnlyDigits is a valid number in the select range */

                    if (OnlyDigits &&
                        ((OnlyDigits = atoi (InputBuffer)) > 0) &&
                        (OnlyDigits <= Count))
                        SetVariableFromString (t->str, AList[OnlyDigits - 1]);

                    else
                        SetVariableFromString (t->str, null);

                    RetVal = ExecuteParseTree (t->left, StandardIN, StandardOUT,
                                               0);
                }
            }

/* Remove this tree from the break list */

            Break_List = S_BList;
            break;

/* While and Until function.  Similar to the For function.  Set up a
 * long jump return point before executing the while function so that
 * the continue statement is executed OK.
 */

        case TWHILE:                    /* WHILE and UNTIL functions    */
        case TUNTIL:
            while ((RetVal = setjmp (BreakContinue.CurrentReturnPoint)) != 0)
            {

/* Restore the current stack level and clear out any I/O */

                RestoreEnvironment (0, Local_depth + 1);
                Return_List = S_RList;
                SShell_List = S_SList;

/* If this is a break, terminate the while and switch statements */

                if (RetVal == BC_BREAK)
                    break;
            }

            if (RetVal == BC_BREAK)
                break;

/* Set up links */

            BreakContinue.NextExitLevel = Break_List;
            Break_List = &BreakContinue;

            while ((ExecuteParseTree (t->left, StandardIN, StandardOUT, 0) == 0)
                                == (t->type == TWHILE))
                RetVal = ExecuteParseTree (t->right, StandardIN, StandardOUT,
                                           0);

            Break_List = S_BList;
            break;

        case TIF:                       /* IF and ELSE IF functions     */
        case TELIF:
            if (t->right != (C_Op *)NULL)
                RetVal = ExecuteParseTree (!ExecuteParseTree (t->left,
                                                              StandardIN,
                                                              StandardOUT, 0)
                                            ? t->right->left : t->right->right,
                                            StandardIN, StandardOUT, 0);

            break;

        case TCASE:                     /* CASE function                */
        {
            C_Op        *ts = t->left;

            cp = ExpandAString (t->str, 0);

            while ((ts != (C_Op *)NULL) && (ts->type == TPAT))
            {
                for (AList = ts->vars; *AList != NOWORD; AList++)
                {
                    if ((EndIB = ExpandAString (*AList, EXPAND_PATTERN)) != 0 &&
                        GeneralPatternMatch (cp, (unsigned char *)EndIB, FALSE,
                                             (char **)NULL, GM_ALL))
                        goto Found;
                }

                ts = ts->right;
            }

            break;

Found:
            RetVal = ExecuteParseTree (ts->left, StandardIN, StandardOUT, 0);
            break;
        }

        case TBRACE:                    /* {} statement                 */
            if ((RetVal >= 0) && (t->left != (C_Op *)NULL))
                RetVal = ExecuteParseTree (t->left, StandardIN, StandardOUT,
                                           (Actions & EXEC_FUNCTION));

            break;
    }

/* Processing Completed - Restore environment */

    Execute_stack_depth = Local_depth;

/* Remove unwanted malloced space */

    ReleaseMemoryArea (MemoryAreaLevel);
    MemoryAreaLevel = Local_MemoryAreaLevel;

/* Check for traps */

    if (t->type == TCOM)
    {
        RunTrapCommand (-1);            /* Debug trap                   */

        if (RetVal)
            RunTrapCommand (-2);        /* Err trap                     */
    }

/* Interrupt traps */

    if ((Count = InterruptTrapPending) != 0)
    {
        InterruptTrapPending = 0;
        RunTrapCommand (Count);
    }

/* Check for interrupts */

    if (InteractiveFlag && IS_TTY (0) && SW_intr)
    {
        CloseAllHandlers ();
        TerminateCurrentEnvironment (TERMINATE_COMMAND);
    }

    return RetVal;
}


/*
 * Restore the original directory
 */
void RestoreCurrentDirectory (char *path)
{
    SetCurrentDrive (GetDriveNumber (*path));

    if (!S_chdir (&path[2]))
    {
        if (!FL_TEST (FLAG_WARNING))
            feputs ("Warning: current directory reset to /\n");

        S_chdir (DirectorySeparator);
        GetCurrentDirectoryPath ();
    }
}


/*
 * Ok - execute the program, resetting any I/O required
 */
static int F_LOCAL
ForkAndExecute (
    C_Op *t, int StandardIN, int StandardOUT,
     int ForkAction, char **AList, char **VList, char **LastWord )
{
    int                 RetVal = -1;    /* Return value                 */
    int                 (*shcom)(int, char **) = (int (*)(int, char **))NULL;
    char                *cp;
    char                **alp;
    IO_Actions          **iopp = t->ioact;
    int                 builtin = 0;    /* Builtin function             */
    bool                CGVLCalled = FALSE;
    bool                InternalExec = FALSE;
    int                 Index;

    if (t->type == TCOM)
    {
        cp = *AList;

/* strip all initial assignments not correct wrt PATH=yyy command  etc */

        if (FL_TEST (FLAG_PRINT_EXECUTE) ||
           ((CurrentFunction != (FunctionList *)NULL) &&
            CurrentFunction->Traced))
            EchoCurrentCommand (cp != NOWORD ? AList : VList);

/* Is it only an assignement? */

        if ((cp == NOWORD) && (t->ioact == (IO_Actions **)NULL))
        {
            while (((cp = *(VList++)) != NOWORD) &&
                   AssignVariableFromString (cp, &Index))
                continue;

#if (OS_TYPE != OS_DOS)
            ExitWithJobsActive = FALSE;
#endif

/* Get the status variable if expand changed it */

            return (int)GetVariableAsNumeric (StatusVariable);
        }

/* Check for built in commands */

        else if (cp != NOWORD)
        {
            shcom = IsCommandBuiltIn (cp, &builtin);
            InternalExec = C2bool ((strcmp (cp, LIT_exec)) == 0);

/*
 * Reset the ExitWithJobsActive flag to enable the 'jobs active' on exit
 * message
 */

#if (OS_TYPE != OS_DOS)
            if (strcmp (cp, LIT_exit))
                ExitWithJobsActive = FALSE;
#endif
        }

#if (OS_TYPE != OS_DOS)
        else
            ExitWithJobsActive = FALSE;
#endif
    }

#if (OS_TYPE != OS_DOS)
    else
        ExitWithJobsActive = FALSE;
#endif

/*
 * UNIX fork simulation?
 */

/* If there is a command to execute or we are exec'ing and this is not a
 * TPAREN, save the current environment
 */

    if (t->type != TPAREN)
    {
        if (((*VList != NOWORD) &&
             ((builtin & BLT_SKIPENVIR) != BLT_SKIPENVIR)) ||
            (ForkAction & EXEC_WITHOUT_FORK))
        {
            if (CreateGlobalVariableList (FLAGS_FUNCTION) == -1)
                return -1;

            CGVLCalled = TRUE;
        }

/* Set up any variables.  Note there is an assumption that
 * AssignVariableFromString sets the equals sign to 0, hiding the value;
 */

        if (shcom == (int (*)(int, char **))NULL)
        {
            while (((cp = *(VList++)) != NOWORD) &&
                   AssignVariableFromString (cp, &Index))
                SetVariableArrayStatus (cp, Index,
                                        (STATUS_EXPORT | STATUS_LOCAL));

/* If the child shells from this process communicate via pipes, set the
 * environment so the child shell will know.  This is a special case for OS/2
 * (and Win NT?) EMACS.
 */

           if (ExecProcessingMode.Flags & EP_PSEUDOTTY)
           {
               char     temp[30];

               AssignVariableFromString (strcat (strcpy (temp, LIT_AllowTTY),
                                                 "=true"), &Index);
               SetVariableArrayStatus (temp, Index,
                                       (STATUS_EXPORT | STATUS_LOCAL));
           }
        }
    }


/* If this is the first command in a pipe, set stdin to /dev/null */

   if ((ForkAction & (EXEC_PIPE_IN | EXEC_PIPE_SUBS)) == EXEC_PIPE_IN)
   {
        if ((Index = S_open (FALSE, "/dev/null", O_RDONLY)) != 0)
        {
            S_dup2 (Index, 0);
            S_close (Index, TRUE);
        }
   }

/* We cannot close the pipe, because once the exec/spawn has taken place
 * the processing of the pipe is not yet complete.
 */

    if (StandardIN != NOPIPE)
    {
        S_dup2 (StandardIN, STDIN_FILENO);
        /*lseek (STDIN_FILENO, 0L, SEEK_SET);*/
    }

    if (StandardOUT != NOPIPE)
    {
        FlushStreams ();
        S_dup2 (StandardOUT, STDOUT_FILENO);
        lseek (STDOUT_FILENO, 0L, SEEK_END);
    }

/* Set up any other IO required */

    FlushStreams ();

    if (iopp != (IO_Actions **)NULL)
    {
        while (*iopp != (IO_Actions *)NULL)
        {
            if (SetUpIOHandlers (*(iopp++), StandardIN, StandardOUT))
                return RetVal;
        }
    }


/* All fids above 10 are autoclosed in the exec file because we have used
 * the O_NOINHERIT flag.  Note I patched open.obj to pass this flag to the
 * open function.
 */

    if (t->type == TPAREN)
        return RestoreStandardIO (ExecuteParseTree (t->left, NOPIPE, NOPIPE, 0),
                                  TRUE);

/* Are we just changing the I/O re-direction for the shell ? */

    if (*AList == NOWORD)
    {
        if ((ForkAction & EXEC_WITHOUT_FORK) == 0)
            RestoreStandardIO (0, TRUE);

/* Get the status variable if expand changed it */

        return (int)GetVariableAsNumeric (StatusVariable);
    }

/*
 * Find the end of the parameters and set up $_ environment variable
 */

    alp = AList;
    while (*alp != NOWORD)
       alp++;

/* Move back to last parameter, and save it */

    *LastWord = StringCopy (*(alp - 1));

/* No - Check for a function the program.  At this point, we need to put
 * in some processing for return.
 */

    if (!(builtin & BLT_CURRENT) &&
            ExecuteFunction (AList, &RetVal, CGVLCalled))
        return RetVal;

/* Check for another drive or directory in the restricted shell */

    if ((strpbrk (*AList, ":/\\") != (char *)NULL) &&
            CheckForRestrictedShell (*AList))
        return RestoreStandardIO (-1, TRUE);

/* A little cheat to allow us to use the same code to start OS/2 sessions
 * as to load and execute a program
 */

#if (OS_TYPE == OS_OS2)
    SessionControlBlock = (STARTDATA *)NULL;
#endif

/* Ok - execute the program */

    if (!(builtin & BLT_CURRENT))
    {
        RetVal = EnvironExecute (AList, ForkAction);

        if (ExecProcessingMode.Flags != EP_ENVIRON)
            RetVal = LocalExecve (AList, BuildCommandEnvironment(), ForkAction);
    }

/* If we didn't find it, check for internal command
 *
 * Note that the exec command is a special case
 */

    if ((builtin & BLT_CURRENT) || ((RetVal == -1) && (errno == ENOENT)))
    {
        if (shcom != (int (*)(int, char **))NULL)
        {
            if (InternalExec)
                RetVal = doexec (t);

            else
                RetVal = (*shcom)(CountNumberArguments (AList), AList);

            SetCommandReturnStatus (RetVal);
        }
    }

    if (RetVal == -1)
        PrintLoadError (*AList);

    return RestoreStandardIO (RetVal, TRUE);
}

/*
 * Restore Local Environment
 */

void RestoreEnvironment (int retval, int stack)
{
    Execute_stack_depth = stack;
    DeleteGlobalVariableList ();
    RestoreCurrentDirectory (CurrentDirectory->value);
    RestoreStandardIO (SetCommandReturnStatus (retval), TRUE);
}

/*
 * Set up I/O redirection.  0< 1> are ignored as required within pipelines.
 */

static bool F_LOCAL SetUpIOHandlers (IO_Actions *iop,
                                     int        pipein,
                                     int        pipeout)
{
    int         u = -1;
    char        *cp = 0;
    char        *msg;

/* Check for pipes */

    if ((pipein != NOPIPE) && (iop->io_unit == STDIN_FILENO))
        return FALSE;

    if ((pipeout != NOPIPE) && (iop->io_unit == STDOUT_FILENO))
        return FALSE;

    msg = ((iop->io_flag & IOTYPE) == IOWRITE) ? "create" : "open";

    if ((iop->io_flag & IOTYPE) != IOHERE)
        cp = ExpandOneStringFirstComponent (iop->io_name,
                                            EXPAND_TILDE | EXPAND_GLOBBING);

/*
 * Duplicate - check for close
 */

    if ((iop->io_flag & IOTYPE) == IODUP)
    {
        if ((cp[1]) || (!isdigit (*cp) && (*cp != CHAR_CLOSE_FD)))
        {
            ShellErrorMessage ("illegal >& argument (%s)", cp);
            return TRUE;
        }

        if (*cp == CHAR_CLOSE_FD)
        {
            iop->io_flag &= ~IOTYPE;
            iop->io_flag |= IOCLOSE;
        }
    }

/*
 * When writing to /dev/???, we have to cheat because MSDOS appears to
 * have a problem with /dev/ files after find_first/find_next.
 */

#if (OS_TYPE != OS_UNIX)
    if (((iop->io_flag & IOTYPE) == IOWRITE) &&
        (strnicmp (cp, DeviceNameHeader, LEN_DEVICE_NAME_HEADER) == 0))
    {
        iop->io_flag &= ~IOTYPE;
        iop->io_flag |= IOCAT;
    }
#endif

/* Open the file in the appropriate mode */

    switch (iop->io_flag & IOTYPE)
    {
        case IOREAD:                            /* <                    */
            u = S_open (FALSE, cp, O_RDONLY);
            break;

        case IOHERE:                            /* <<                   */
            u = OpenHereFile (iop->io_name, C2bool (iop->io_flag & IOEVAL));
            cp = "here file";
            break;

        case IORDWR:                            /* <>                   */
            if (CheckForRestrictedShell (cp))
                return TRUE;

            u = S_open (FALSE, cp, O_RDWR);
            break;

        case IOCAT:                             /* >>                   */
            if (CheckForRestrictedShell (cp))
                return TRUE;

            if ((u = S_open (FALSE, cp, O_WRONLY | O_BINARY)) >= 0)
            {
                lseek (u, 0L, SEEK_END);
                break;
            }

        case IOWRITE:                           /* >                    */
            {
                if (CheckForRestrictedShell (cp))
                    return TRUE;

                if ((ShellGlobalFlags & FLAGS_NOCLOBER) &&
                        (!(iop->io_flag & IOCLOBBER)) &&
                        (S_access (cp, F_OK)))
                {
                   u = -1;
                   break;
                }
            }
            u = S_open (FALSE, cp, O_CMASK);
            break;

        case IODUP:                             /* >&                   */
            if (CheckForRestrictedShell (cp))
                return TRUE;

            u = S_dup2 (*cp - '0', iop->io_unit);
            break;

        case IOCLOSE:                           /* >-                   */
            if ((iop->io_unit >= STDIN_FILENO) &&
                (iop->io_unit <= STDERR_FILENO))
                S_dup2 (-1, iop->io_unit);

            S_close (iop->io_unit, TRUE);
            return FALSE;
    }

    if (u < 0)
    {
        PrintWarningMessage (LIT_3Strings, cp, "cannot ", msg);
        return TRUE;
    }

    else if (u != iop->io_unit)
    {
        S_dup2 (u, iop->io_unit);
        S_close (u, TRUE);
    }

    return FALSE;
}

/*
 * -x flag - echo command to be executed
 */

static void F_LOCAL EchoCurrentCommand (char **wp)
{
    int         i;

    if ((CurrentFunction != (FunctionList *)NULL) && CurrentFunction->Traced)
        fprintf (stderr, "%s:", CurrentFunction->tree->str);

    feputs (GetVariableAsString (PS4, TRUE));

    for (i = 0; wp[i] != NOWORD; i++)
    {
        if (i)
            feputc (CHAR_SPACE);

        feputs (wp[i]);
    }

    feputc (CHAR_NEW_LINE);
}


/*
 * Set up the status on exit from a command
 */
static int F_LOCAL
SetCommandReturnStatus (int s)
{
    SetVariableFromNumeric (StatusVariable, (long) abs (ExitStatus = s));
    return s;
}


/*
 * Execute a command
 */
int
ExecuteACommand(
    char **argv, int mode )
{
    int         RetVal;

    CheckProgramMode (*argv, &ExecProcessingMode);
    RetVal = EnvironExecute (argv, 0);

    if (ExecProcessingMode.Flags != EP_ENVIRON)
        RetVal = LocalExecve (argv, BuildCommandEnvironment (), mode);

    return RetVal;
}


/*
 * PATH-searching interface to execve.
 */
static int F_LOCAL
LocalExecve(
    char **argv, char **envp, int ForkAction )
{
    int                 res = -1;               /* Result               */
    char                *p_name;                /* Program name         */
    int                 i;

/* Clear the DosExec Failname field */

#if (OS_TYPE == OS_OS2)
    *FailName = 0;
#endif

#if (OS_TYPE != OS_DOS)
    OS_DosExecPgmReturnCode = 0;
#endif

/* If the environment is null - It is too big - error */

    if (envp == NOWORDS)
        errno = E2BIG;

    else if ((p_name = AllocateMemoryCell (FFNAME_MAX)) == (char *)NULL)
        errno = ENOMEM;

    else
    {
/* Start off on the search path for the executable file */

        switch (i = FindLocationOfExecutable (p_name, argv[0]))
        {
            case EXTENSION_EXECUTABLE:
                res = ExecuteProgram (p_name, argv, envp, ForkAction);
                break;

/* Script file */

            case EXTENSION_BATCH:
            case EXTENSION_SHELL_SCRIPT:
                res = ExecuteScriptFile (p_name, argv, envp, ForkAction,
                         (bool)(i == EXTENSION_SHELL_SCRIPT));
                break;
        }

        if (res != -1)
        {
            TrackAllCommands (p_name, *argv);
            return res;
        }
    }

/* If no fork - exit */

    if (ForkAction & EXEC_WITHOUT_FORK)
    {
        PrintLoadError (*argv);
        FinalExitCleanUp (-1);
    }

    return -1;
}


/*
 * Exec or spawn the program ?
 */
static int F_LOCAL
ExecuteProgram (
    char *path, char **parms, char **envp, int  ForkAction)
{
    int                 res;

/* Check we have access to the file */

    if (!S_access (path, F_OK))
        return SetCommandReturnStatus (-1);

/* Process the command line */

    res = BuildCommandLine (path, parms, envp, ForkAction);

    SetWindowName ((char *)NULL);
    ClearExtendedLineFile ();
    return SetCommandReturnStatus (res);
}


/* Set up command line.  If the EXTENDED_LINE variable is set, we create
 * a temporary file, write the argument list (one entry per line) to the
 * this file and set the command line to @<filename>.  If NOSWAPPING, we
 * execute the program because I have to modify the argument line
 */
static int F_LOCAL
BuildCommandLine (
    char *path, char **argv, char **envp, int ForkAction)
{
    char                **pl = argv;
    FILE                *fd;
    bool                found;
    char                *new_args[3];
    char                cmd_line[FFNAME_MAX];

/* Translate process name to MSDOS format */

    if ((GenerateFullExecutablePath (path) == (char *)NULL) ||
        (!GetApplicationType (path)))
        return -1;

/* If this is a windows program - special */

#if (OS_TYPE != OS_UNIX)
    if ((ApplicationType == EXETYPE_DOS_GUI) && (BaseOS != BASE_OS_NT))
        return ExecuteWindows (path, argv, envp, ForkAction);
#endif

/* Extended command line processing */

    Extend_file = (char *)NULL;         /* Set no file          */
    found = C2bool ((ExecProcessingMode.Flags & EP_UNIXMODE) ||
                    (ExecProcessingMode.Flags & EP_DOSMODE));

/* Set up a blank command line */

    cmd_line[0] = 0;
    cmd_line[1] = CHAR_RETURN;

/* If there are no parameters, or they fit in the DOS command line
 * - start the process */

    if ((*(++pl) == (char *)NULL) || CheckParameterLength (pl))
        return StartTheProcess (path, argv, envp, ForkAction);

/* If we can use an alternative approach - indirect files, use it */

    else if (found)
    {
        char    **pl1 = pl;

/* Check parameters don't contain a re-direction parameter */

        while (*pl1 != NOWORD)
        {
            if (**(pl1++) == CHAR_INDIRECT)
            {
               if (found && !FL_TEST (FLAG_WARNING))
                    PrintWarningMessage (
"sh: cannot exec, existing redirect(s) %c located within arg list.", CHAR_INDIRECT );

                found = FALSE;
                errno = E2BIG;
                break;
            }
        }

/* If we find it - create a temporary file and write the stuff */

        if ((found) &&
            ((fd = FOpenFile (Extend_file = GenerateTemporaryFileName (),
                              sOpenWriteMode)) != (FILE *)NULL))
        {
            if (FL_TEST(FLAG_DEBUG_EXECUTE))
                fprintf (stderr, "Creating temporary file (%s)\n", Extend_file);

            if ((Extend_file = StringSave (Extend_file)) == null)
                Extend_file = (char *)NULL;

/* Copy to end of list */

            do {
                if (!WriteToExtendedFile (fd, *pl))
                    return -1;
            } while (*(pl++) != NOWORD);

/* Set up cmd_line[1] to contain the filename */

            memset (cmd_line, 0, FFNAME_MAX);
            cmd_line[1] = CHAR_SPACE;
            cmd_line[2] = CHAR_INDIRECT;
            strcpy (&cmd_line[3], Extend_file);
            cmd_line[0] = (char)(strlen (Extend_file) + 2);

/* If the name in the file is in upper case - use \ for separators */

            if (ExecProcessingMode.Flags & EP_DOSMODE)
                PATH_TO_DOS (&cmd_line[2]);

/* OK we are ready to execute */

            new_args[0] = *argv;
            new_args[1] = &cmd_line[2];
            new_args[2] = (char *)NULL;

            return StartTheProcess (path, new_args, envp, ForkAction);
        }
    }

    return -1;
}

/*
 * Clear Extended command line file
 */

void ClearExtendedLineFile (void)
{
    if (Extend_file != (char *)NULL)
    {
        if (!FL_TEST(FLAG_DEBUG_EXECUTE))       /* leave temporary images? */
            unlink (Extend_file);               
        ReleaseMemoryCell ((void *)Extend_file);
    }

    Extend_file = (char *)NULL;
}

/*
 * Convert the executable path to the full path name
 */
char *
GenerateFullExecutablePath (char *path)
{
    char                cpath[PATH_MAX + 6];
    char                npath[FFNAME_MAX + 2];
    char                n1path[PATH_MAX + 6];
    char                *p;
    int                 drive;

    PATH_TO_UPPER_CASE (path);

/* Get the current path */

    S_getcwd (cpath, 0);
    strcpy (npath, cpath);

/* In current directory ? */

    if ((p = FindLastPathCharacter (path)) == (char *)NULL)
    {
         p = path;

/* Check for a:program case */

         if (IsDriveCharacter (*(p + 1)))
         {
            p += 2;

/* Get the path of the other drive */

            S_getcwd (npath, GetDriveNumber (*path));
         }
    }

/* In root directory */

    else if ((p - path) == 0)
    {
        ++p;
        strcpy (npath, RootDirectory);
        *npath = *path;
        *npath = *cpath;
    }

    else if (((p - path) == 2) && IsDriveCharacter (*(path + 1)))
    {
        ++p;
        strcpy (npath, RootDirectory);
        *npath = *path;
    }

/* Find the directory */

    else
    {
        *(p++) = 0;

/* Change to the directory containing the executable */

        drive = IsDriveCharacter (*(path + 1))
                        ? GetDriveNumber (*path)
#if (OS_TYPE == OS_OS2) && !defined (__WATCOMC__)
                        : _getdrive ();
#else
                        : 0;
#endif

/* Save the current directory on this drive */

        S_getcwd (n1path, drive);

/* Find the directory we want */

        if (!S_chdir (path))
            return (char *)NULL;

        S_getcwd (npath, drive);                /* Save its full name */
        S_chdir (n1path);                       /* Restore the original */

/* Restore our original directory */

        if (!S_chdir (cpath))
            return (char *)NULL;
    }

    if (!IsPathCharacter (npath[strlen (npath) - 1]))
        strcat (npath, DirectorySeparator);

    strcat (npath, p);
    p = PATH_TO_DOS (strcpy (path, npath));
    return p;
}


/*
 * Find the number of values to use for a for or select statement
 */
static char ** F_LOCAL
FindNumberOfValues (char **wp, int *Count)
{

/* select/for x do...done - use the parameter values.  Need to know how many as
 * it is not a NULL terminated array
 */

    if (wp == NOWORDS)
    {
        if ((*Count = ParameterCount) < 0)
            *Count = 0;

        return ParameterArray + 1;
    }

/* select/for x in y do...done - find the start of the variables and
 * use them all
 */

    *Count = CountNumberArguments (wp);

    return wp;
}


/*
 * Count the number of entries in an array
 */
int CountNumberArguments (char **wp)
{
    int         Count = 0;

    while (*(wp++) != NOWORD)
        Count++;

    return Count;
}


/*
 * Write a command string to the extended file
 */
static bool F_LOCAL
WriteToExtendedFile (FILE *fd, char *string)
{
    char        *sp = string;
    char        *cp = string;
    bool        WriteOk = TRUE;

    if (string == (char *)NULL)
    {
        if (CloseFile (fd) != EOF)
            return TRUE;

        WriteOk = FALSE;
    }

    else if (strlen (string))
    {

/* Write the string, converting newlines to backslash newline */

        while (WriteOk && (cp != (char *)NULL))
        {
            if ((cp = strchr (sp, CHAR_NEW_LINE)) != (char *)NULL)
                *cp = 0;

            if (fputs (sp, fd) == EOF)
                WriteOk = FALSE;

            else if (cp != (char *)NULL)
                WriteOk = C2bool (fputs ("\\\n", fd) != EOF);

            sp = cp + 1;
        }
    }

    if (WriteOk && (fputc (CHAR_NEW_LINE, fd) != EOF))
        return TRUE;

    CloseFile (fd);
    ClearExtendedLineFile ();
    errno = ENOSPC;
    return FALSE;
}

/*
 * Execute or spawn the process
 */

/* DOS, 16/32 bit version */

#if (OS_TYPE == OS_DOS)
static int F_LOCAL
StartTheProcess(
    char *path, char **argv, char **envp, int ForkAction )
{
    int         retval;

    __systeml_sh = 1;                           /* 27/09/00 */
    if (FL_TEST(FLAG_DEBUG_EXECUTE))
        __systeml_debug = 1;                    /* 25/09/00 */

    if (ForkAction & EXEC_WITHOUT_FORK)
    {
        DumpHistory ();                         /* Exec - save history */

        if ((retval = long_execvpe(path, argv, envp)) != -1)
            exit (retval);
        PrintErrorMessage ("unexpected return from exec (%d)", errno);
        return retval;
    }
    return long_spawnvpe(path, argv, envp);
}
#endif


/* OS/2 Version */

#if (OS_TYPE == OS_OS2)
static int F_LOCAL
StartTheProcess (
    char *path, char **argv, char **envp, int ForkAction)
{
    int                 RetVal;
    STARTDATA           stdata;

/* Is this a start session option */

    if (SessionControlBlock != (STARTDATA *)NULL)
        return StartTheSession (SessionControlBlock, path, argv, envp,
                                ForkAction);

/* Exec ? */

    if (ForkAction & EXEC_WITHOUT_FORK)
        return OS_DosExecProgram (OLD_P_OVERLAY, path, argv, envp);

    if (ForkAction & (EXEC_SPAWN_DETACH | EXEC_SPAWN_NOWAIT |
                      EXEC_SPAWN_IGNOREWAIT))
    {
        int     Mode = (ForkAction & EXEC_SPAWN_DETACH)
                        ? P_DETACH
                        : ((ForkAction & EXEC_SPAWN_IGNOREWAIT)
                           ? P_NOWAITO
                           : (FL_TEST (FLAG_SEPARATE_GROUP)
                             ? P_DETACH
                             : P_NOWAIT));

        RetVal = OS_DosExecProgram (Mode, path, argv, envp);

/* Remove the reference to the temporary file for background tasks */

        ReleaseMemoryCell ((void *)Extend_file);
        Extend_file = (char *)NULL;

        if ((RetVal != -1) && (Mode != P_NOWAITO))
        {
            if (InteractiveFlag && (source->type == STTY))
            {
                if (ForkAction & EXEC_SPAWN_DETACH)
                    fprintf (stderr, "[0] Process %d detached\n", RetVal);

                else
                {
                    PidInfo.Valid = TRUE;
                    PidInfo.JobNo = AddNewJob (RetVal, 0, path);
                    PidInfo.PidNo = RetVal;
                }
            }

            SetVariableFromNumeric ("!", RetVal);
            return 0;
        }

        return RetVal;
    }

/* In OS/2, we need the type of the program because PM programs have to be
 * started in a session (or at least that was the only way I could get them
 * to work).
 */

/* Do we need to start a new session for this program ? */

    if (((ApplicationType & EXETYPE_OS2_TYPE) == EXETYPE_OS2_GUI) ||
        (ApplicationType & EXETYPE_DOS))
    {
        if ((OS_VERS_N > 1) && (ApplicationType & EXETYPE_DOS))
            memcpy (&stdata, &DOS_SessionControlBlock, sizeof (STARTDATA));

        else
            memcpy (&stdata, &PM_SessionControlBlock, sizeof (STARTDATA));

        if (ForkAction & EXEC_WINDOWS)
            stdata.PgmControl = 0;

        return StartTheSession (&stdata, path, argv, envp, ForkAction);
    }

/* Just DosExecPgm it */

    return OS_DosExecProgram (P_WAIT, path, argv, envp);
}
#endif

/* NT Version */

#if (OS_TYPE == OS_NT)
static int F_LOCAL
StartTheProcess (
    char *path, char **argv, char **envp, int ForkAction )
{
    int                 RetVal;

/* Exec ? */

    if (ForkAction & EXEC_WITHOUT_FORK)
        return OS_DosExecProgram (OLD_P_OVERLAY, path, argv, envp);

    if (ForkAction & (EXEC_SPAWN_DETACH | EXEC_SPAWN_NOWAIT |
                      EXEC_SPAWN_IGNOREWAIT))
    {
        int     Mode = (ForkAction & EXEC_SPAWN_DETACH)
                        ? P_DETACH
                        : ((ForkAction & EXEC_SPAWN_IGNOREWAIT)
                           ? P_NOWAITO
                           : (FL_TEST (FLAG_SEPARATE_GROUP)
                             ? P_DETACH
                             : P_NOWAIT));

        RetVal = OS_DosExecProgram (Mode, path, argv, envp);

/* Remove the reference to the temporary file for background tasks */

        ReleaseMemoryCell ((void *)Extend_file);
        Extend_file = (char *)NULL;

        if ((RetVal != -1) && (Mode != P_NOWAITO))
        {
            if (InteractiveFlag && (source->type == STTY))
            {
                if (ForkAction & EXEC_SPAWN_DETACH)
                    fprintf (stderr, "[0] Process %d detached\n", RetVal);

                else
                {
                    PidInfo.Valid = TRUE;
                    PidInfo.JobNo = AddNewJob (RetVal, 0, path);
                    PidInfo.PidNo = RetVal;
                }
            }

            SetVariableFromNumeric ("!", RetVal);
            return 0;
        }

        else
            return RetVal;
    }

/* Just DosExecPgm it */

    return OS_DosExecProgram (P_WAIT, path, argv, envp);
}
#endif


/*
 * Start a session
 */
#if (OS_TYPE == OS_OS2)
static int F_LOCAL
StartTheSession(
    STARTDATA *SessionData, char *path,
      char **argv, char **envp, int ForkAction )
{
    OSCALL_PARAM        usType;
    OSCALL_PARAM        idSession;
#  if (OS_SIZE == OS_32)
    PID                 pid;
#  else
    USHORT              pid;
#  endif

/*
 * For OS/2 2.x, we can start DOS sessions!!
 */
    if ((OS_VERS_N > 1) && (ApplicationType & EXETYPE_DOS))
    {
        if ((ForkAction & EXEC_WINDOWS) ||
            (SessionData->SessionType == SSF_TYPE_FULLSCREEN))
            SessionData->SessionType = SSF_TYPE_VDM;

        else
            SessionData->SessionType = SSF_TYPE_WINDOWEDVDM;

        if (SessionData->Environment == (PBYTE)1)
            SessionData->Environment = (PBYTE)NULL;
    }

    else if ((ApplicationType & EXETYPE_OS2_TYPE) == EXETYPE_OS2_GUI)
        SessionData->SessionType = SSF_TYPE_PM;

    SessionData->PgmName = path;
    SessionData->TermQ = SessionEndQName;       /* Queue name           */

    /*
     * Build the environment if the current value is 1
     */

    if ((SessionData->Environment == (PBYTE)1) &&
        ((SessionData->Environment = (PBYTE)BuildOS2String (envp, 0))
                                   == (PBYTE)NULL))
        return -1;

    ProcessSpaceInParameters (argv);

    if ((SessionData->PgmInputs = (PBYTE)BuildOS2String (&argv[1], CHAR_SPACE))
                                == (PBYTE)NULL)
        return -1;

/*
 * Start the session.  Do not record if it is independent
 */

    DISABLE_HARD_ERRORS;
#  if (OS_SIZE == OS_16) && !defined (OS2_16_BUG)
    SessionData->Related = SSF_RELATED_INDEPENDENT;
#  endif
    usType = DosStartSession (SessionData, &idSession, &pid);
    ENABLE_HARD_ERRORS;

    if ((!usType) || (usType == ERROR_SMG_START_IN_BACKGROUND))
    {
        if (InteractiveFlag && (source->type == STTY))
        {
            if (SessionData->Related == SSF_RELATED_INDEPENDENT)
                fprintf (stderr, "[0] Independent Session %d started\n",
                         idSession);

            else
                fprintf (stderr, "[%d] Session %d (PID %d) started\n",
                         AddNewJob (pid, idSession, path), idSession, pid);

            if (usType)
                fprintf (stderr, "%s\n", GetOSSystemErrorMessage (usType));
        }

        SetVariableFromNumeric ("!", pid);
        return 0;
    }

    else
    {
        strcpy (FailName, GetOSSystemErrorMessage (usType));

        errno = ENOENT;
        return -1;
    }
}
#endif


/*
 * Build the OS2 format <value>\0<value>\0 etc \0
 */
char *
BuildOS2String (char **Array, char sep)
{
    int         i = 0;
    int         Length = 0;
    char        *Output;
    char        *sp, *cp;

/* Find the total data length */

    while ((sp = Array[i++]) != NOWORD)
        Length += strlen (sp) + 1;

    Length += 2;

    if ((Output = AllocateMemoryCell (Length)) == (char *)NULL)
        return (char *)NULL;

/* Build the new string */

    i = 0;
    sp = Output;

/* Build the string */

    while ((cp = Array[i++]) != NOWORD)
    {
        while ((*sp = *(cp++)) != 0)
            ++sp;

        if (!sep || (Array[i] != NOWORD))
            *(sp++) = sep;
    }

    *sp = 0;
    return Output;
}


/*
 * List of default known extensions and their type
 */

#define MAX_DEFAULT_EXTENSIONS  ARRAY_SIZE (DefaultExtensions)

static struct ExtType {
    char        *Extension;
    char        Type;
} DefaultExtensions [] = {
    { null,             EXTENSION_SHELL_SCRIPT },
    { SHELLExtension,   EXTENSION_SHELL_SCRIPT },
    { KSHELLExtension,  EXTENSION_SHELL_SCRIPT },
#if (OS_TYPE != OS_UNIX)
    { BATExtension,     EXTENSION_BATCH },
    { EXEExtension,     EXTENSION_EXECUTABLE },
    { COMExtension,     EXTENSION_EXECUTABLE },
#endif
};

/*
 * Built the list of valid extensions
 */

void
BuildExtensionLists (void)
{
    Word_B      *DList = (Word_B *)NULL;
    Word_B      *FList = (Word_B *)NULL;
    int         i;
    char        *pe;
    char        *sp;
    void        (_SIGDECL *save_signal)(int);

/* Disable signals */

    save_signal = signal (SIGINT, SIG_IGN);

/* Release Old memory */

    if (ExecuteFunctionList != (char **)NULL)
    {
        ReleaseMemoryCell (*ExecuteFunctionList);
        ReleaseMemoryCell (ExecuteFunctionList);
    }

    if (ExecutableList != (char **)NULL)
        ReleaseMemoryCell (ExecutableList);

/* No extensions ? */

    if ((pe = GetVariableAsString (PathExtsLiteral, FALSE)) == null)
    {
        for (i = 0; i < MAX_DEFAULT_EXTENSIONS; i++)
            DList = AddWordToBlock (DefaultExtensions [i].Extension, DList);

        ExecutableList = GetWordList (AddWordToBlock ((char *)NULL, DList));
        ExecuteFunctionList = (char **)NULL;

/* Restore signals */

        signal (SIGINT, save_signal);

        return;
    }

/* OK - scan */

    pe = StringSave (pe);

/* Split out on semi-colons */

    do
    {
        if ((sp = strchr (pe, ';')) != (char *)NULL)
            *(sp++) = 0;

        FList = AddWordToBlock (pe, FList);

/* Build the new order of default extensions */

        for (i = 0; i < MAX_DEFAULT_EXTENSIONS; i++)
        {
            if (!NOCASE_COMPARE (pe, DefaultExtensions [i].Extension))
            {
                DList = AddWordToBlock (pe, DList);
                break;
            }
        }

        pe = sp;
    } while (pe != (char *)NULL);

/* Set up the two lists */

    ExecutableList = GetWordList (AddWordToBlock ((char *)NULL, DList));
    ExecuteFunctionList = GetWordList (AddWordToBlock ((char *)NULL, FList));

/* Restore signals */

    signal (SIGINT, save_signal);
}


/*
 * Find the location of an executable and return it's full path
 * name
 */
int
FindLocationOfExecutable (
    char *FullPath, char *name)
{
    return ExtensionType (FindFileAndExtension (FullPath, name, ExecutableList));
}


/*
 * Return the extension type for an extension
 */
static int F_LOCAL
ExtensionType (char *ext)
{
    int         i;

    if (ext == (char *)NULL)
        return EXTENSION_NOT_FOUND;

    for (i = 0; i < MAX_DEFAULT_EXTENSIONS; i++)
    {
        if (!NOCASE_COMPARE (ext, DefaultExtensions [i].Extension))
            return (int)(DefaultExtensions [i].Type);
    }

    return EXTENSION_OTHER;
}


/*
 * Search the path for a file with the appropriate extension
 *
 * Returns a pointer to the extension.
 */
static char * F_LOCAL
FindFileAndExtension (
    char *FullPath, char *name, char **Extensions )
{
    char        *sp;                    /* Path pointers        */
    char        *ep;
    char        *xp;                    /* In file name pointers */
    char        *xp1;
    int         i, fp;
    int         pathlen;

/* No extensions - no file */

    if (Extensions == (char **)NULL)
    {
        errno = ENOENT;
        return (char *)NULL;
    }

    if (FL_TEST(FLAG_DEBUG_EXECUTE))
    {
        fprintf (stderr, "Searching for: %s (", name);
        for (i = 0; Extensions[i] != (char *)NULL; i++)
            fprintf (stderr, "%s%s", (i ? " " : ""), 
               (Extensions[i][0] ? Extensions[i] : "."));
        fprintf (stderr, ") using\n");
        fprintf (stderr, " PATH=%s\n", GetVariableAsString (PathLiteral, FALSE) );
    }

/* Scan the path for an executable */

    sp = ((FindPathCharacter (name) != (char *)NULL) ||
          (IsDriveCharacter (*(name + 1))))
              ? null : GetVariableAsString (PathLiteral, FALSE);

    do
    {
        sp = BuildNextFullPathName (sp, name, FullPath);
        ep = &FullPath[pathlen = strlen (FullPath)];

        if (FL_TEST(FLAG_DEBUG_EXECUTE))
             fprintf (stderr, " Check: %s\n", FullPath);

/* Get start of file name */

        if ((xp1 = FindLastPathCharacter (FullPath)) == (char *)NULL)
            xp1 = FullPath;

        else
            ++xp1;

/* Look up all 5 types */

        for (i = 0; Extensions[i] != (char *)NULL; i++)
        {
            if (pathlen + strlen (Extensions[i]) > (size_t)(FFNAME_MAX - 1))
                continue;

            strcpy (ep, Extensions[i]);

            if (S_access (FullPath, F_OK))
            {
/* If no extension, .ksh or .sh extension, check for shell script */

                if (((xp = strrchr (xp1, CHAR_PERIOD)) == (char *)NULL) ||
                    (NOCASE_COMPARE (xp, SHELLExtension) == 0) ||
                    (NOCASE_COMPARE (xp, KSHELLExtension) == 0))
                {

                    if ((fp = CheckForScriptFile (FullPath, (char **)NULL,
                                                  (int *)NULL)) < 0) {
                        if (FL_TEST(FLAG_DEBUG_EXECUTE))
                              fprintf (stderr, "Unable to determine type\n" );
                        continue;
                    }

                    S_close (fp, TRUE);

                    if (FL_TEST(FLAG_DEBUG_EXECUTE))
                         fprintf (stderr, " Match: script %s\n", FullPath);

                    return (xp == (char *)NULL) ? null : xp;
                }

                if (FL_TEST(FLAG_DEBUG_EXECUTE))
                    fprintf (stderr, " Match: binary %s\n", FullPath);

                return xp;
            }
        }
    } while (sp != (char *)NULL);

/* Not found */

    errno = ENOENT;
    return (char *)NULL;
}


/*
 * Execute a script file
 */
static int F_LOCAL
ExecuteScriptFile (
    char *Fullpath, char **argv, char **envp, int ForkAction, bool ShellScript)
{
    int         res;                    /* Result               */
    char        *params = 0;            /* Script parameters    */
    int         nargc = 0;              /* # script args        */
    Word_B      *wb = (Word_B *)NULL;
#if (OS_TYPE == OS_DOS) && (OS_SIZE == OS_16)
    union REGS  r;
#endif

/* Batfile - convert to DOS Format file name */

    if (!ShellScript)
    {
        PATH_TO_DOS (Fullpath);
        nargc = 0;
    }

    else if ((res = OpenForExecution (Fullpath, &params, &nargc)) >= 0)
        S_close (res, TRUE);

    else
    {
        errno = ENOENT;
        return -1;
    }

/* If BAT file, use command.com else use sh */

    if (!ShellScript)
    {
        if (!SetUpCLI (GetVariableAsString (ComspecVariable, FALSE), &wb))
            return -1;

        wb = AddWordToBlock ("/c", wb);

/* Get the switch character */

#if (OS_TYPE == OS_DOS) && (OS_SIZE == OS_16)
        r.x.REG_AX = 0x3700;
        DosInterrupt (&r, &r);

        if ((r.h.al == 0) && (_osmajor < 4))
            *(wb->w_words[wb->w_nword - 1]) = (char)(r.h.dl);
#endif
    }

/* Stick in the pre-fix arguments */

    else if (nargc)
        wb = SplitString (params, wb);

    else if (params != null)
        wb = AddWordToBlock (params, wb);

    else
        wb = AddWordToBlock (GetVariableAsString (ShellVariableName, FALSE),
                             wb);

/* Add the rest of the parameters, and execute */

    res = ExecuteSpecialProcessor (Fullpath, argv, envp, ForkAction, wb);

/* Release allocated space */

    if (params != null)
        ReleaseMemoryCell ((void *)params);

    return res;
}


/*
 * Convert errno to error message on execute
 */
static char * F_LOCAL
ConvertErrorNumber (void)
{
    switch (errno)
    {
        case ENOMEM:
            return strerror (ENOMEM);

        case ENOEXEC:
            return "program corrupt";

        case E2BIG:
            return AE2big;

        case ENOENT:
            return NotFound;

        case 0:
            return "No Shell";
    }

    return "cannot execute";
}


/*
 * Convert UNIX format lines to DOS format if appropriate.
 * Build Environment variable for some programs.
 */
static int F_LOCAL
EnvironExecute (char **argv, int ForkAction)
{
    int         s_errno;
    int         RetVal = 1;
    char        *NewArgs[3];
    char        *cp;

/* If this command does not pass the command string in the environment,
 * no action required
 */

    if (ExecProcessingMode.Flags != EP_ENVIRON)
        return 0;

    if ((cp = BuildOS2String (&argv[1], ExecProcessingMode.FieldSep))
                 == (char *)NULL)
    {
        ExecProcessingMode.Flags = EP_NONE;
        return 0;
    }

    SetVariableFromString (ExecProcessingMode.Name, cp);
    SetVariableStatus (ExecProcessingMode.Name, STATUS_EXPORT);

/* Build and execute the environment */

    NewArgs[0] = argv[0];
    NewArgs[1] = ExecProcessingMode.Name;
    NewArgs[2] = (char *)NULL;

    RetVal = LocalExecve (NewArgs, BuildCommandEnvironment (), ForkAction);
    s_errno = errno;
    UnSetVariable (ExecProcessingMode.Name, -1, FALSE);
    errno = s_errno;
    return RetVal;
}


/*
 * Check Parameter line length
 *
 * Under OS2, we don't build the command line.  Just check it.
 */
static bool F_LOCAL
CheckParameterLength (char **argv)
{
    int         CmdLineLength;
    char        *CommandLine;
    bool        RetVal = FALSE;
    char        **SavedArgs = argv;
    int         i;

/* Check for special case.  If there are any special characters and we can
 * use UNIX mode, use it
 */

    if (FL_TEST(FLAG_DEBUG_EXECUTE))
    {
        fprintf (stderr, " Mode: ");
        PrintProgramMode (&ExecProcessingMode);

        fprintf (stderr, " args: Raw dump ...\n");
        for (i = 0; argv[i]; i++)
        {
            fprintf (stderr, " args: [%02d] %s.\n", i, argv[i]);
        }

    }

    if (ExecProcessingMode.Flags & EP_UNIXMODE)
    {
        for (i = 0; argv[i]; i++)
            if (strpbrk (argv[i], WildCards) != (char *)NULL)
            {
                if (FL_TEST(FLAG_DEBUG_EXECUTE))
                    fprintf (stderr, " args: contains wild cards @%02d.\n",i);

                return FALSE;
            }
    }

#if (OS_TYPE == OS_NT)
    if (ApplicationType & EXETYPE_NT)
    {
        if (FL_TEST(FLAG_DEBUG_EXECUTE))
            fprintf (stderr, " args: NT target.\n");

        return TRUE;                            /* APY, 05/05/04 */
    }
#endif

/*
 * Do any parameter conversion - adding quotes or backslashes, but don't
 * update argv.
 */

    CmdLineLength = CountNumberArguments (argv = SavedArgs);

    if ((SavedArgs = (char **)
                AllocateMemoryCell ((CmdLineLength + 1) * sizeof (char *)))
                == (char **)NULL)
        return FALSE;

/* Save a copy of the argument addresses */

    memcpy (SavedArgs, argv, (CmdLineLength + 1) * sizeof (char *));

/* Build the command line */

    if ((CommandLine = BuildOS2String (ProcessSpaceInParameters (SavedArgs),
                CHAR_SPACE)) == (char *)NULL)
    {
        ReleaseMemoryCell (SavedArgs);
        return FALSE;
    }

/*
 * Check command line length. Under DOS, this is simple.  On OS/2, we have
 * to remember that we default to the EMX interface, which requires
 * twice as much space, plus some nulls.  Also remember start DOS programs
 * on OS/2 or NT have restricted space.
 *
 * HACK:        When a DOS extender assume 1/3 the command line
 *              is needed by the stub (eg DOS4G).
 */
    CmdLineLength = strlen (CommandLine);

    if ((ApplicationType & EXETYPE_DOS) &&
         ((CmdLineLength >= CMD_LINE_MAX - 2) ||
          ((ExecProcessingMode.Flags & EP_DOSEXT) && CmdLineLength >= (CMD_LINE_MAX/3*2))) )
    {
        errno = E2BIG;
    }

#if (OS_TYPE == OS_OS2)
    else if (CmdLineLength >= (((CMD_LINE_MAX - 2) / 2) +
                CountNumberArguments (SavedArgs) + 3))
    {
        errno = E2BIG;
    }

#elif (OS_TYPE == OS_NT)
    else if (CmdLineLength >= CMD_LINE_MAX - 2)
    {
        errno = E2BIG;
    }
#endif

/* Terminate the line */

    else
    {
        RetVal = TRUE;
    }

    if (!RetVal && FL_TEST(FLAG_DEBUG_EXECUTE))
        fprintf (stderr, " args: length exceeds command line.\n");

    ReleaseMemoryCell (SavedArgs);
    ReleaseMemoryCell (CommandLine);

    return RetVal;
}


/*
 * Convert any parameters with spaces in the to start and end with double
 * quotes.
 *
 * Under OS2, the old string is NOT released.
 */

static char ** F_LOCAL
ProcessSpaceInParameters (char **argv)
{
    char        **Start = argv;
    char        *new;
    char        *cp;
    char        *sp;
    int         Count;

/* If noquote set, don't even try */

    if (ExecProcessingMode.Flags & EP_NOQUOTE)
        return Start;

/* Protect parameters with TABS */

    while (*argv != (char *)NULL)
    {
        if ((strpbrk (*argv, " \t") != (char *)NULL) ||
            (strlen (*argv) == 0) ||
            ((ExecProcessingMode.Flags & EP_QUOTEWILD) &&
             (strpbrk (*argv, WildCards) != (char *)NULL)))
        {

/* Count number of Double quotes in the parameter */

            Count = CountDoubleQuotes (*argv);

/* Get some memory - give up update if out of memory */

            if ((new = GetAllocatedSpace (strlen (*argv) + (Count * 2) +
                                          3)) == (char *)NULL)
                return Start;

            SetMemoryAreaNumber ((void *)new,
                                 GetMemoryAreaNumber ((void *)*argv));
            *new = CHAR_DOUBLE_QUOTE;

/* Escape any double quotes in the string */

            cp = *argv;
            sp = new + 1;

            while (*cp)
            {
                if (*cp == CHAR_DOUBLE_QUOTE)
                {
                    *(sp++) = CHAR_META;
                    *(sp++) = *(cp++);
                }

                else if (*cp != CHAR_META)
                    *(sp++) = *(cp++);

/* Handle escapes - count them */

                else
                {
                    *(sp++) = *(cp++);

                    if (*cp == CHAR_DOUBLE_QUOTE)
                    {
                        *(sp++) = CHAR_META;
                        *(sp++) = CHAR_META;
                    }

                    else if (*cp == 0)
                    {
                        *(sp++) = CHAR_META;
                        break;
                    }

                    *(sp++) = *(cp++);
                }
            }

/* Append the terminating double quotes */

            strcpy (sp, DoubleQuotes);
            *argv = new;
        }

/* Check for any double quotes */

        else if ((Count = CountDoubleQuotes (*argv)) != 0)
        {

/* Got them - escape them */

            if ((new = GetAllocatedSpace (strlen (*argv) + Count + 1))
                        == (char *)NULL)
                return Start;

            SetMemoryAreaNumber ((void *)new,
                                 GetMemoryAreaNumber ((void *)*argv));

/* Copy the string, escaping DoubleQuotes */

            cp = *argv;
            sp = new;

            while ((*sp = *(cp++)) != 0)
            {
                if (*sp == CHAR_DOUBLE_QUOTE)
                {
                    *(sp++) = CHAR_META;
                    *sp = CHAR_DOUBLE_QUOTE;
                }

                sp++;
            }

            *argv = new;
        }

/* Next parameter */

        argv++;
    }

    return Start;
}


/*
 * Count DoubleQuotes
 */
static int F_LOCAL
CountDoubleQuotes (char *string)
{
    int         Count = 0;

    while ((string = strchr (string, CHAR_DOUBLE_QUOTE)) != (char *)NULL)
    {
        Count++;
        string++;
    }

    return Count;
}


/*
 * Save and Restore the Parameters array ($1, $2 etc)
 */
static void F_LOCAL
SaveNumericParameters (char **wp, SaveParameters *SaveArea)
{
    SaveArea->Array = ParameterArray;
    SaveArea->Count = ParameterCount;

    ParameterArray = wp;
    for (ParameterCount = 0; ParameterArray[ParameterCount] != NOWORD;
         ++ParameterCount)
        continue;

    SetVariableFromNumeric (ParameterCountVariable, (long)--ParameterCount);
}

static void F_LOCAL
RestoreTheParameters (SaveParameters *SaveArea)
{
    ParameterArray = SaveArea->Array;
    ParameterCount = SaveArea->Count;
    SetVariableFromNumeric (ParameterCountVariable, (long)ParameterCount);
}


/*
 * Special OS/2 processing for execve and spawnve
 */
#if (OS_TYPE == OS_OS2)
static int F_LOCAL
OS_DosExecProgram (
    int Mode, char *Program, char **argv, char **envp)
{
    OSCALL_PARAM        fExecFlags;
    void                (_SIGDECL *sig_int)();           /* Interrupt signal     */
    RESULTCODES         rescResults;
    PID                 pidProcess;
    PID                 pidWait;
    char                *OS2Environment;
    char                *OS2Arguments;
    char                **SavedArgs;
    int                 argc;

/* Set the error module to null */

    *FailName = 0;
    OS_DosExecPgmReturnCode = 0;

/* Convert spawn mode to DosExecPgm mode */

    switch (Mode)
    {
        case P_WAIT:
        case P_NOWAIT:
            fExecFlags = EXEC_ASYNCRESULT;
            break;

        case P_NOWAITO:
        case OLD_P_OVERLAY:
            fExecFlags = EXEC_ASYNC;
            break;

        case P_DETACH:
            fExecFlags = EXEC_BACKGROUND;
            break;
    }

/* Build OS/2 argument string
 *
 * 1.  Count the number of arguments.
 * 2.  Add 2 for: 1 - NULL; 2 - argv[0]; 3 - the stringed arguments
 * 3.  save copy of arguments at offset 2.
 * 4.  On original argv, process white space and convert to OS2 Argument string.
 * 5.  Set up program name at offset 2 as ~argv
 * 6.  Convert zero length args to "~" and args beginning with ~ to ~~
 * 7.  Build OS2 Argument string (at last).
 */

    argc = CountNumberArguments (argv);

    if ((SavedArgs = (char **)AllocateMemoryCell ((argc + 3) * sizeof (char *)))
            == (char **)NULL)
        return -1;

    memcpy (SavedArgs + 2, argv, (argc + 1) * sizeof (char *));

/* Set program name at Offset 0 */

    SavedArgs[0] = *argv;

/* Build OS2 Argument string in Offset 1 */

    if ((SavedArgs[1] = BuildOS2String (ProcessSpaceInParameters (&argv[1]),
                                        CHAR_SPACE)) == (char *)NULL)
        return -1;

/* Set up the new arg 2 - ~ + programname */

    if ((SavedArgs[2] = InsertCharacterAtStart (*argv)) == (char *)NULL)
        return -1;

/* Convert zero length args and args starting with a ~ */

    for (argc = 3; SavedArgs[argc] != NOWORD; argc++)
    {
        if (strlen (SavedArgs[argc]) == 0)
            SavedArgs[argc] = "~";

        else if ((*SavedArgs[argc] == CHAR_TILDE) &&
                 ((SavedArgs[argc] = InsertCharacterAtStart (SavedArgs[argc]))
                        == (char *)NULL))
            return -1;
    }

/* Build the full argument list */

    if ((OS2Arguments = BuildOS2String (SavedArgs, 0)) == (char *)NULL)
        return -1;

/* Build OS/2 environment string */

    if ((OS2Environment = BuildOS2String (envp, 0)) == (char *)NULL)
        return -1;

/* Change signal handling */

    if (fExecFlags == EXEC_ASYNCRESULT)
    {
        char    *cp;

/* Also change the window title to match the foreground program */

        if ((cp = strrchr (Program, CHAR_DOS_PATH)) == (char *)NULL)
            cp = Program;

        else
            cp++;

        strcpy (FailName, cp);

        if ((cp = strrchr (FailName, CHAR_PERIOD)) != (char *)NULL)
            *cp = 0;

        SetWindowName (FailName);
        *FailName = 0;

        IgnoreInterrupts = TRUE;
        sig_int = signal (SIGINT, SIG_IGN);
    }

/* Exec it - with hard-error processing turned off */

    DISABLE_HARD_ERRORS;

    OS_DosExecPgmReturnCode = DosExecPgm (FailName, sizeof (FailName),
                                          fExecFlags, OS2Arguments,
                                          OS2Environment, &rescResults,
                                          Program);

    ENABLE_HARD_ERRORS;

    if (fExecFlags == EXEC_ASYNCRESULT)
    {
        signal (SIGINT, SIG_IGN);

/* If the process started OK, wait for it */

        if (((OS_DosExecPgmReturnCode == NO_ERROR) ||
             (OS_DosExecPgmReturnCode == ERROR_INTERRUPT)) &&
            (Mode == P_WAIT))
        {
            pidWait = rescResults.codeTerminate;

/* Re-try on interrupted system calls - and kill the child.  Why, because
 * sometimes the kill does not go to the child
 */

            while ((OS_DosExecPgmReturnCode = DosCwait (DCWA_PROCESSTREE,
                                                        DCWW_WAIT,
                                                        &rescResults,
                                                        &pidProcess,
                                                        pidWait))
                                             == ERROR_INTERRUPT)
                DosKillProcess (DKP_PROCESS, pidWait);
        }

        IgnoreInterrupts = FALSE;
        signal (SIGINT, sig_int);
    }


/*
 * What happened ?.  OS/2 Error - Map to UNIX errno.  Why can't people
 * write libraries right??  Or provide access at a high level.  We could
 * call _dosret if the interface did not require me to write more
 * assembler.
 */

    if (OS_DosExecPgmReturnCode != 0)
    {
        switch (OS_DosExecPgmReturnCode)
        {
#ifdef EAGAIN
            case ERROR_NO_PROC_SLOTS:
                errno = EAGAIN;
                return -1;
#endif

            case ERROR_NOT_ENOUGH_MEMORY:
                errno = ENOMEM;
                return -1;

            case ERROR_ACCESS_DENIED:
            case ERROR_DRIVE_LOCKED:
            case ERROR_LOCK_VIOLATION:
            case ERROR_SHARING_VIOLATION:
                errno = EACCES;
                return -1;

            case ERROR_FILE_NOT_FOUND:
            case ERROR_PATH_NOT_FOUND:
            case ERROR_PROC_NOT_FOUND:
                errno = ENOENT;
                return -1;

            case ERROR_BAD_ENVIRONMENT:
            case ERROR_INVALID_DATA:
            case ERROR_INVALID_FUNCTION:
            case ERROR_INVALID_ORDINAL:
            case ERROR_INVALID_SEGMENT_NUMBER:
            case ERROR_INVALID_STACKSEG:
            case ERROR_INVALID_STARTING_CODESEG:
                errno = EINVAL;
                return -1;

            case ERROR_TOO_MANY_OPEN_FILES:
                errno = EMFILE;
                return -1;

            case ERROR_INTERRUPT:
            case ERROR_NOT_DOS_DISK:
            case ERROR_SHARING_BUFFER_EXCEEDED:
                errno = EIO;
                return -1;

            case ERROR_BAD_ARGUMENTS:
                errno = E2BIG;
                return -1;

            default:
                errno = ENOEXEC;
                return -1;
        }
    }

/* If exec - exit */

    switch (Mode)
    {
        case OLD_P_OVERLAY:                     /* exec - exit at once */
            FinalExitCleanUp (0);

        case P_WAIT:                            /* Get exit code        */
            return rescResults.codeResult;

        case P_NOWAIT:                          /* Get PID or SID       */
        case P_NOWAITO:
        case P_DETACH:
            return rescResults.codeTerminate;
    }

    errno = EINVAL;
    return -1;
}


/*
 * Insert character at start of string
 *
 * Return NULL or new string
 */

static char     *InsertCharacterAtStart (char *string)
{
    char        *cp;

    if ((cp = (char *)AllocateMemoryCell (strlen (string) + 2)) == (char *)NULL)
        return (char *)NULL;

    *cp = CHAR_TILDE;
    strcpy (cp + 1, string);

    return cp;
}
#endif  /*OS2*/


/*
 * Special NT processing for execve and spawnve.  Not really sure what to
 * do here yet!
 */
#if (OS_TYPE == OS_NT)
static int F_LOCAL
OS_DosExecProgram (
    int Mode, char *Program, char **argv, char **envp )
{
#if (REMOVED)
    DWORD                       dwLogicalDrives = GetLogicalDrives();
    char                        szNewDrive[4];
    char                        *nt_ep;
    char                        *temp;
    unsigned int                i;
#endif
    char                        **new_envp;
    char                        ldir[PATH_MAX + 6];
    int                         off = 0;
    STARTUPINFO                 StartupInfo;
    PROCESS_INFORMATION         ProcessInformation;
    char                        *Environment;
    char                        *PgmInputs;
    BOOL                        rv;
    int                         ExitCode;

    OS_DosExecPgmReturnCode = 0;

/*
 * Set up NT directory variables
 */

    if ((new_envp = (char **)GetAllocatedSpace (GetMemoryCellSize (envp) +
                                                sizeof (char *) * 26))
                  == (char **)NULL)
    {
        errno = ENOMEM;
        return -1;
    }

#if (REMOVED)
    strcpy (szNewDrive, "x:");

    for (i = 0; i < 25; i++)
    {
        if (dwLogicalDrives & (1L << i))
        {
            szNewDrive[0] = (char)(i + 'A');

/* If the drive does not exist - give up */

            DISABLE_HARD_ERRORS;
            ExitCode = GetFullPathName (szNewDrive, PATH_MAX + 6, ldir, &temp);
            ENABLE_HARD_ERRORS;

            if (!ExitCode)
                continue;

            if ((nt_ep = GetAllocatedSpace (strlen (ldir) + 5)) == (char *)NULL)
            {
                errno = ENOMEM;
                return -1;
            }

            sprintf (nt_ep, "=%c:=%s", i + 'A', ldir);
            new_envp[off++] = nt_ep;
        }
    }
#endif  /*REMOVED*/

    memcpy (&new_envp[off], envp, GetMemoryCellSize (envp));

/* Setup environment block */

    if ((Environment = BuildOS2String (new_envp, 0)) == (char *)NULL)
        return -1;

/* Setup parameter block */

    ProcessSpaceInParameters (argv);

    if (strchr(Program, ' ') == NULL || Program[0] == '"')
        argv[0] = Program;                      /* no spaces or quoted */
    else
    {                                           /* must quote .. i hate WIN */
        (void)sprintf( ldir, "\"%s\"", Program );
        argv[0] = ldir;
    }

    if ((PgmInputs = BuildOS2String (argv, CHAR_SPACE)) == (char *)NULL)
        return -1;

/* Set up startup info */

    memset (&StartupInfo, 0, sizeof (StartupInfo));

    StartupInfo.cb = sizeof (StartupInfo);
    StartupInfo.lpDesktop = NULL;
    StartupInfo.lpTitle = NULL;
    StartupInfo.dwFlags = 0;

/* Change Window name ? */

    if (Mode == P_WAIT)
    {
        char    *cp;
        char    *Name;

/* Also change the window title to match the foreground program */

        if ((cp = strrchr (Program, CHAR_DOS_PATH)) == (char *)NULL)
            cp = Program;

        else
            cp++;

        Name = StringCopy (cp);

        if ((cp = strrchr (Name, CHAR_PERIOD)) != (char *)NULL)
            *cp = 0;

        SetWindowName (Name);
        ReleaseMemoryCell (Name);
    }

/* Exec it - with hard-error processing turned off */

    if (FL_TEST(FLAG_DEBUG_EXECUTE))
        fprintf (stderr, "CreateProcess(%s,%s)\n", Program, PgmInputs);

    DISABLE_HARD_ERRORS;
    rv = CreateProcess (
                Program, PgmInputs, NULL, NULL, TRUE,
                NORMAL_PRIORITY_CLASS,
                Environment, NULL,
                &StartupInfo, &ProcessInformation );
    ENABLE_HARD_ERRORS;

    if (!rv)
    {
        OS_DosExecPgmReturnCode = GetLastError ();
        errno = ENOENT;
        return -1;
    }

    CloseHandle(ProcessInformation.hThread);

    if (Mode == P_WAIT)
    {
        if (WaitForSingleObject(
                ProcessInformation.hProcess, INFINITE) == WAIT_OBJECT_0)
        {
            DWORD exitcode = 0;

            GetExitCodeProcess(ProcessInformation.hProcess, &exitcode);
            ExitCode = (int) exitcode;
            return ExitCode;
        }
        else
        {
            OS_DosExecPgmReturnCode = GetLastError ();
            errno = EINVAL;
            ExitCode = -1;
        }
        CloseHandle(ProcessInformation.hProcess);
        return (ExitCode);
    }

/* Otherwise, return the process ID. */

    return (ProcessInformation.dwProcessId);
}
#endif  /*OS_NT*/


/*
 * Execute a Function
 */
static bool F_LOCAL
ExecuteFunction (
     char **wp, int *RetVal, bool CGVLCalled)
{
    Break_C                     *s_RList = Return_List;
    Break_C                     *s_BList = Break_List;
    Break_C                     *s_SList = SShell_List;
    Break_C                     BreakContinue;
    C_Op                        *New;
    FunctionList                *s_CurrentFunction = CurrentFunction;
    SaveParameters              s_Parameters;
    FunctionList                *fop;
    GetoptsIndex                GetoptsSave;
    bool                        TrapZeroExists;
    char                        *Ext;
    char                        *FullPath;

    TrapZeroExists = C2bool (GetVariableAsString ("~0", FALSE) != null);

/* Find the extension type - if it exists */

    FullPath = GetAllocatedSpace (FFNAME_MAX);

/* Check for a psuedo-function */

    switch (ExtensionType (Ext = FindFileAndExtension (FullPath,
                                                       wp[0],
                                                       ExecuteFunctionList)))
    {
        case EXTENSION_OTHER:
            if ((fop = LookUpFunction (Ext, TRUE)) == (FunctionList *)NULL)
                return FALSE;

            wp[0] = FullPath;
            break;

/* Common extension for all .sh, .ksh and "" scripts.  If we find one, use
 * it, otherwise, check for a function of this name
 */

        case EXTENSION_SHELL_SCRIPT:
            Ext = ".ksh";

        case EXTENSION_BATCH:
            if ((fop = LookUpFunction (Ext, TRUE)) != (FunctionList *)NULL)
            {
                wp[0] = FullPath;
                break;
            }

/* Check for a real function */

        case EXTENSION_NOT_FOUND:
        case EXTENSION_EXECUTABLE:
        default:
            if ((fop = LookUpFunction (wp[0], FALSE)) == (FunctionList *)NULL)
                return FALSE;
    }

/* Ok, we really have a function to execute.
 * Save the current variable list
 */

    if (!CGVLCalled && (CreateGlobalVariableList (FLAGS_FUNCTION) == -1))
    {
        *RetVal =  -1;
        return TRUE;
    }

/* Set up $0..$n for the function */

    SaveNumericParameters (wp, &s_Parameters);

/* Save Getopts pointers */

    GetGetoptsValues (&GetoptsSave);

/* Process the function */

    if (setjmp (BreakContinue.CurrentReturnPoint) == 0)
    {
        CurrentFunction = fop;
        Break_List = (Break_C *)NULL;
        BreakContinue.NextExitLevel = Return_List;
        Return_List = &BreakContinue;
        New = CopyFunction (fop->tree->left);
        *RetVal = ExecuteParseTree (New, NOPIPE, NOPIPE, EXEC_FUNCTION);
    }

/* A return has been executed - Unlike, while and for, we just need to
 * restore the local execute stack level and the return will restore
 * the correct I/O.
 */

    else
        *RetVal = (int)GetVariableAsNumeric (StatusVariable);

    if (!TrapZeroExists)
        RunTrapCommand (0);             /* Exit trap                    */

/* Restore the old $0, and previous return address */

    SaveGetoptsValues (GetoptsSave.Index, GetoptsSave.SubIndex);
    Break_List  = s_BList;
    Return_List = s_RList;
    SShell_List = s_SList;
    CurrentFunction = s_CurrentFunction;
    RestoreTheParameters (&s_Parameters);

    return TRUE;
}


/*
 * Print Load error message
 */

#if (OS_TYPE == OS_OS2)
static void F_LOCAL PrintLoadError (char *path)
{
    if (*FailName)
        PrintWarningMessage ("%s: %s\nSYS1804: Cannot find file - %s", path,
                             ConvertErrorNumber(), FailName);

    else if (OS_DosExecPgmReturnCode)
        PrintWarningMessage ("%s: %s\n%s", path, ConvertErrorNumber(),
                     GetOSSystemErrorMessage (OS_DosExecPgmReturnCode));

    else
        PrintWarningMessage ("%s: %s", path, ConvertErrorNumber());
}
#endif

/* NT Version */

#if (OS_TYPE == OS_NT)
static void F_LOCAL PrintLoadError (char *path)
{
    if (OS_DosExecPgmReturnCode)
        PrintWarningMessage ("%s: %s\n%s", path, ConvertErrorNumber(),
                     GetOSSystemErrorMessage (OS_DosExecPgmReturnCode));

    else
        PrintWarningMessage ("%s: %s", path, ConvertErrorNumber());
}
#endif

/* DOS Version */

#if (OS_TYPE == OS_DOS)
static void F_LOCAL PrintLoadError (char *path)
{
    PrintWarningMessage (BasicErrorMessage, path, ConvertErrorNumber ());
}
#endif

/* DOS Version */

#if (OS_TYPE == OS_UNIX)
static void F_LOCAL PrintLoadError (char *path)
{
    PrintWarningMessage (BasicErrorMessage, path, ConvertErrorNumber ());
}
#endif

/*
 * Make the exported environment from the exported names in the dictionary.
 * Keyword assignments will already have been done.  Convert to MSDOS
 * format if flag set and m enabled
 */
static char ** F_LOCAL
BuildCommandEnvironment (void)
{
/* Update SECONDS and RANDOM */

    HandleSECONDandRANDOM ();

/* Build the environment by walking the tree */

    BCE_WordList = (Word_B *)NULL;
    BCE_Length   = 0;

    twalk (VariableTree, BuildEnvironmentEntry);

    if (BCE_Length >= 0x7f00)
        return (char **)NULL;

    return GetWordList (AddWordToBlock (NOWORD, BCE_WordList));
}


/*
 * TWALK Function - Build Export VARIABLE list from VARIABLE tree
 */
static void
BuildEnvironmentEntry (const void *key, VISIT visit, int level)
{
    VariableList        *vp = *(VariableList **)key;
    char                *cp;
    char                *sp;
    int                 tlen;

    (void) level;

    if ((visit != postorder) && (visit != leaf))
        return;

    if ((vp->status & STATUS_EXPORT) && (vp->index == 0))
    {
        cp = GetVariableAsString (vp->name, TRUE);
        tlen = strlen (vp->name) + strlen (cp) + 2;

        if ((BCE_Length += tlen) >= 0x7f00)
            return;

        strcpy ((sp = GetAllocatedSpace (tlen)), vp->name);
        SetMemoryAreaNumber ((void *)sp, MemoryAreaLevel);
        strcat (sp, "=");
        strcat (sp, cp);

        BCE_WordList = AddWordToBlock (sp, BCE_WordList);

/* If MSDOS mode, we need to copy the variable, convert / to \ and put
 * the copy in the environment list instead
 */

        if (((ShellGlobalFlags & FLAGS_MSDOS_FORMAT) ||
             (ExecProcessingMode.Flags & EP_EXPORT)) &&
            (vp->status & STATUS_CONVERT_MSDOS))
        {
            cp = StringCopy (BCE_WordList->w_words[BCE_WordList->w_nword - 1]);
            BCE_WordList->w_words[BCE_WordList->w_nword - 1] = PATH_TO_DOS (cp);
        }
    }
}


/*
 * Parse and Execute a command in the current shell
 */
int
RunACommand (Source *s, char **params)
{
    jmp_buf             erp;
    int                 RetVal = -1;
    Break_C             *S_RList = Return_List; /* Save loval links     */
    Break_C             *S_BList = Break_List;
    int                 LS_depth = Execute_stack_depth++;
    C_Op                *outtree;
    bool                s_ProcessingEXECCommand = ProcessingEXECCommand;
    SaveParameters      s_Parameters;

/* Create a new save area */

    MemoryAreaLevel++;

/* Set up $0..$n for the command if appropriate.  Note that $0 does not
 * change
 */

    if (params != NOWORDS)
    {
        SaveNumericParameters (params, &s_Parameters);
        ParameterArray[0] = s_Parameters.Array[0];
    }

/* Execute the command */

    CreateNewEnvironment ();

    Return_List = (Break_C *)NULL;
    Break_List  = (Break_C *)NULL;
    ProcessingEXECCommand = TRUE;
    e.ErrorReturnPoint = (ErrorPoint)NULL;

    if (SetErrorPoint (erp) == 0)
    {

/* Read Input until completed */

        for (;;)
        {
            if (((outtree = BuildParseTree (s)) != (C_Op *)NULL) &&
                (outtree->type == TEOF))
                break;

            RetVal = ExecuteParseTree (outtree, NOPIPE, NOPIPE, 0);
        }
    }

    QuitCurrentEnvironment ();

/* Restore the environment */

    ClearExtendedLineFile ();
    Return_List = S_RList;
    Break_List = S_BList;
    ProcessingEXECCommand = s_ProcessingEXECCommand;

/* Restore $0..$n */

    if (params != NOWORDS)
        RestoreTheParameters (&s_Parameters);

    RestoreEnvironment (RetVal, LS_depth);
    ReleaseMemoryArea (MemoryAreaLevel--);
    return RetVal;
}


/*
 * Get the OS/2 Error message
 */
#if (OS_TYPE == OS_OS2)
char *
GetOSSystemErrorMessage (OSCALL_RET code)
{
    static char         Buffer [FFNAME_MAX + 9];
    char                *Buffer1;
    OSCALL_PARAM        MsgLength;
    OSCALL_RET          rc;
    char                *ip;
    char                *op = Buffer;
    char                *sp;
    int                 len;

/* For some reason DPATH does not work with the DosGetMessage API as the
 * spec say it should on OS/2 2.x.  Probably something we do.  It usually
 * is!  So emulate it!
 */

    if ((len = strlen (sp = GetVariableAsString ("DPATH", FALSE))) < FFNAME_MAX)
        len = FFNAME_MAX;

    if ((Buffer1 = GetAllocatedSpace (len)) == (char *)NULL)
    {
        sprintf (Buffer, "SYS%.4d: No memory to get message text", code);
        return Buffer;
    }

    sp = PATH_TO_UNIX (strcpy (Buffer1, sp));

    do
    {
        sp = BuildNextFullPathName (sp, "OSO001.MSG", Buffer);
    } while ((access (Buffer, F_OK) != 0) && (sp != (char *)NULL));

/* If not found - use the default */

    if (sp == (char *)NULL)
    {
        strcpy (Buffer, "c:/OS2/SYSTEM/OSO001.MSG");
        *Buffer = GetDriveLetter (GetRootDiskDrive ());
    }

/* Read the message */

    if ((rc = DosGetMessage (NULL, 0, ip = Buffer1, len, code, Buffer,
                            &MsgLength)))
        sprintf (Buffer, "SYS%.4d: No error message available (%d)",
                 code, rc);

    else
    {
        if ((Buffer1[MsgLength - 1] == CHAR_NEW_LINE) &&
            (Buffer1[MsgLength - 2] == CHAR_RETURN))
            Buffer1[MsgLength - 2] = 0;

        else
            Buffer1[MsgLength] = 0;

/* Check the error number is there */

        if (strncmp (Buffer1, "SYS", 3) != 0)
        {
            sprintf (op, "SYS%.4d: ", code);
            op += strlen (op);
        }

/* Remove interior CRs & NLs */

        while ((*(op) = *(ip++)))
        {
            if (*op == CHAR_NEW_LINE)
                *(op++) = CHAR_SPACE;

            else if (*op != CHAR_RETURN)
                op++;
        }
    }

    ReleaseMemoryCell (Buffer1);
    return Buffer;
}


#  ifdef __WATCOMC__
/*
 * A cheat for WATCOM which does not have the DosGetMessage API.  This
 * function is based an interpretation of the System message file and one or
 * two others.  The format of the header is not know except for the flag at
 * offset 0x0f.  It is not guaranteed to work.
 */
APIRET APIENTRY
DosGetMessage(
    PCHAR *ppchVTable, ULONG usVCount, PCHAR pchBuf,
      ULONG cbBuf, ULONG usMsgNum, PSZ pszFileName, PULONG pcbMsg )
{
    int                         fd;
#  pragma pack (1)
    union {
        struct {
            USHORT              Start;
            USHORT              End;
        }                       ShortE;
        struct {
            ULONG               Start;
            ULONG               End;
        }                       LongE;
    }                           Start, Current;
#  pragma pack ()
    char                        Type;
    int                         Len;
    unsigned long               Offset;
    APIRET                      Res;

    if ((fd = open (pszFileName, O_RDONLY | O_BINARY)) < 0)
        return _doserrno;

/* Get the message file format */

    if ((lseek (fd, 0x0fL, SEEK_SET) != 0x0fL) ||
        (read (fd, &Type, 1) != 1) ||
        (lseek (fd, 0x1fL, SEEK_SET) != 0x1fL))
    {
        Res = _doserrno;
        close (fd);
        return Res;
    }

/* Read the start of message text location */

    Len = (Type) ? 4 : 8;

    if (read (fd, &Start, Len) != Len)
    {
        Res = _doserrno;
        close (fd);
        return Res;
    }

/* Check the offset to the message */

    Offset = 0x1fL + (usMsgNum * ((Type) ? 2L : 4L));

    if (((Type) && (Offset >= Start.ShortE.Start)) ||
        ((!Type) && (Offset >= Start.LongE.Start)))
    {
        close (fd);
        return ERROR_MR_MID_NOT_FOUND;
    }

/* Get the message location */

    if ((lseek (fd, Offset, SEEK_SET) != Offset) ||
        (read (fd, &Current, Len) != Len))
    {
        Res = _doserrno;
        close (fd);
        return Res;
    }

    if (((Type) && (Offset == Start.ShortE.Start - 2)) ||
            ((!Type) && (Offset == Start.LongE.Start - 4)))
    {
        if ((Offset = lseek (fd, 0L, SEEK_END)) == -1L)
        {
            Res = _doserrno;
            close (fd);
            return Res;
        }

        else if (Type)
            Current.ShortE.End = (USHORT)Offset;

        else
            Current.LongE.End = Offset;
    }

/* Get the message length */

    if (Type)
    {
        *pcbMsg = Current.ShortE.End - Current.ShortE.Start;
        Offset = Current.ShortE.Start;
    }

    else
    {
        *pcbMsg = (USHORT)(Current.LongE.End - Current.LongE.Start);
        Offset = Current.LongE.Start;
    }

/* Check the message length */

    *pcbMsg += 8;

    if (*pcbMsg >= cbBuf)
        return ERROR_MR_MSG_TOO_LONG;

    sprintf (pchBuf, "SYS%.4d: ", usMsgNum);

/* Seek to the start of the message and read its type */

    if ((lseek (fd, Offset, SEEK_SET) != Offset) ||
        (read (fd, &Type, 1) != 1))
    {
        Res = _doserrno;
        close (fd);
        return Res;
    }

    if (Type == '?')
    {
        close (fd);
        return ERROR_MR_MID_NOT_FOUND;
    }

/* Get the message itself */

    else if (read (fd, pchBuf + 9, *pcbMsg - 8) != *pcbMsg - 8)
    {
        Res = _doserrno;
        close (fd);
        return Res;
    }

    close (fd);
    *(pchBuf + *pcbMsg) = 0;
    return 0;
}
#  endif
#endif


/*
 * Get the Win NT Error message
 */
#if (OS_TYPE == OS_NT)
char *
GetOSSystemErrorMessage (OSCALL_RET code)
{
    DWORD               Source = 0;
    static char         EBuffer[100];
    char                *OSBuffer;
    char                *Buffer1;
    char                *ip;
    char                *op;

/* Read the message */

    if (!FormatMessage ((FORMAT_MESSAGE_FROM_SYSTEM |
                         FORMAT_MESSAGE_ALLOCATE_BUFFER), &Source, code, 0,
                        (LPSTR)&OSBuffer, 100, NULL))
    {
        sprintf (EBuffer, "SYS%.4d: No error message available (%ld)",
                 code, GetLastError ());
        OSBuffer = EBuffer;
    }

    else
    {
        op = &OSBuffer[strlen (OSBuffer) - 1];

        while (isspace (*op) && (op != OSBuffer))
            op--;
    }

/* Allocate local space.  If there isn't any give up and hope for the best */

    if ((Buffer1 = GetAllocatedSpace (strlen (OSBuffer) + 20)) == (char *)NULL)
        return OSBuffer;

/* Transfer the system buffer to a local buffer.
 * Check the error number is there
 */

    op = Buffer1;
    ip = OSBuffer;

    if (strncmp (OSBuffer, "SYS", 3) != 0)
    {
        sprintf (op, "SYS%.4d: ", code);
        op += strlen (op);
    }

/* Remove interior CRs & NLs */

    while ((*(op) = *(ip++)) != 0)
    {
        if (*op == CHAR_NEW_LINE)
            *(op++) = CHAR_SPACE;

        else if (*op != CHAR_RETURN)
            op++;
    }

    if (OSBuffer != EBuffer)
        LocalFree (OSBuffer);

    return Buffer1;
}
#endif


/*
 * Display a started job info
 */
#if (OS_TYPE != OS_DOS)
static void F_LOCAL
PrintPidStarted (void)
{
    if (PidInfo.Valid)
        fprintf (stderr, "[%d] %d\n", PidInfo.JobNo, PidInfo.PidNo);

    PidInfo.Valid = FALSE;
}
#endif


/*
 * Print a list of names, with or without numbers
 */
void PrintAList (int ArgCount, char **ArgList)
{
    char        **pp = ArgList;
    int         i, j;
    int         ix;
    int         MaxArgWidth = 0;
    int         NumberWidth = 0;
    int         ncols;
    int         nrows;

    if (!ArgCount)
        return;

/* get dimensions of the list */

    while (*pp != (char *)NULL)
    {
        i = strlen (*(pp++)) + 1;
        MaxArgWidth = (i > MaxArgWidth) ? i : MaxArgWidth;
    }

/*
 * We print an index of the form
 *      %d)
 * in front of each entry.  Get the max width of this
 */

    for (i = ArgCount, NumberWidth = 1; i >= 10; i /= 10)
        NumberWidth++;

/* In the case of numbered lists, we go down if less than screen length */

    if (ArgCount < (MaximumLines - 5))
    {
        ncols = 1;
        nrows = ArgCount;
    }

    else
    {
        ncols = MaximumColumns / (MaxArgWidth + NumberWidth + 3);
        nrows = ArgCount / ncols;

        if (ArgCount % ncols)
            nrows++;

        if (!nrows)
            nrows = 1;

        if (ncols > nrows)
        {
            nrows = ncols;
            ncols = 1;
        }
    }

/* Display the list */

    for (i = 0; i < nrows; i++)
    {
        for (j = 0; j < ncols; j++)
        {
            if ((ix = j * nrows + i) < ArgCount)
            {
                printf ("%*d) ", NumberWidth, ix + 1);

                if (j != (ncols - 1))
                    printf ("%-*.*s", MaxArgWidth, MaxArgWidth, ArgList[ix]);

                else
                    printf ("%-s", ArgList[ix]);
            }
        }

        fputchar (CHAR_NEW_LINE);
    }
}

/*
 * Are we tracking all commands?  Check there is no path in the command
 */
static void F_LOCAL
TrackAllCommands (char *path, char *arg)
{
    if (FL_TEST (FLAG_TRACK_ALL) &&
        (FindPathCharacter (arg) == (char *)NULL) &&
        (!IsDriveCharacter (arg[1])))
        SaveAlias (arg, PATH_TO_UNIX (path), TRUE);
}


/*
 * Get the application type and get we can do it
 */
static bool F_LOCAL
GetApplicationType (char *path)
{
    ApplicationType = QueryApplicationType (path);

    if (FL_TEST(FLAG_DEBUG_EXECUTE)) 
        WhenceTypeDebug (ApplicationType);

/* Some type of error */

    if (ApplicationType & EXETYPE_ERROR)
    {
        if (ApplicationType == EXETYPE_UNKNOWN)
        {
            if (!FL_TEST (FLAG_WARNING))
                fprintf (stderr, "sh: Cannot determine executable type <%s>\n",
                         path);

            return TRUE;
        }

        else if (ApplicationType == EXETYPE_BAD_FILE)
            errno = ENOENT;

        else
            errno = ENOEXEC;

        return FALSE;
    }

/*
 * This is where it gets complicated - Sort out DOS!
 */
#if (OS_TYPE == OS_DOS)
    if (ExecProcessingMode.Flags & EP_IGNTYPE)
    {
        ApplicationType = EXETYPE_DOS_CUI;
        return TRUE;
    }

    else if (ApplicationType == EXETYPE_DOS_GUI)
    {
        if ((BaseOS == BASE_OS_WIN) &&
            (GetVariableAsString (LIT_STARTWINP, FALSE) == null))
        {
            if (!FL_TEST (FLAG_WARNING))
                feputs ("sh: Start this applications from Windows\n");

            errno = ENOEXEC;
            return FALSE;
        }
    }

    else if ((ApplicationType == EXETYPE_DOS_32) && (BaseOS == BASE_OS_NT))
        return BadApplication ("DOS 32-bit");

    else if (ApplicationType & EXETYPE_OS2)
        return BadApplication ("OS/2");

    else if (ApplicationType & EXETYPE_NT)
        return (BaseOS == BASE_OS_NT) ? TRUE : BadApplication ("Win NT");

#elif (OS_TYPE == OS_OS2)
    if (ApplicationType & EXETYPE_NT)
        return BadApplication ("Win NT");

# if (OS_SIZE == OS_16)
    if (OS_VERS_N < 2)
    {
        if (ApplicationType & EXETYPE_DOS)
            return BadApplication (LIT_dos);

        else if (ApplicationType & EXETYPE_OS2_32)
            return BadApplication ("OS/2 32-bit");
    }
#  endif

    if (ApplicationType == EXETYPE_DOS_32)
        return BadApplication ("DOS 32-bit");

#elif (OS_TYPE == OS_NT)
    if (ApplicationType == EXETYPE_DOS_32)
        return BadApplication ("DOS 32-bit");

/*  Yes we can ,,,,,
 *  else if (ApplicationType & EXETYPE_OS2_32)
 *      return BadApplication ("OS/2 32-bit");
 */
#endif

/* In the end - execute it */

    return TRUE;
}


/*
 * Print Bad application warning
 */
static bool F_LOCAL
BadApplication (char *mes)
{
    if (!FL_TEST (FLAG_WARNING))
        fprintf (stderr, "sh: Cannot execute %s applications\n", mes);

    errno = ENOEXEC;
    return FALSE;
}


/*
 * Handle a Windows Program
 */
#if (OS_TYPE != OS_UNIX)
static int F_LOCAL
ExecuteWindows (
    char *Fullpath, char **argv, char **envp, int  ForkAction )
{
    int         res;
#if (OS_TYPE == OS_DOS)
    Word_B      *wb = (Word_B *)NULL;

    if (!SetUpCLI ((BaseOS != BASE_OS_WIN)
                        ? "win"
                        : GetVariableAsString (LIT_STARTWINP, FALSE), &wb))
        return -1;

#elif (OS_TYPE == OS_OS2)
    Word_B      *wb = AddWordToBlock ("winos2", (Word_B *)NULL);
#elif (OS_TYPE == OS_NT)
    Word_B      *wb = (Word_B *)NULL;
#endif

/* Add the rest of the parameters and execute */

    ForkAction |= EXEC_WINDOWS;

    res = ExecuteSpecialProcessor (Fullpath, argv, envp, ForkAction, wb);

/* A cheat to stop us exec'ing the program again.  Normally,
 * ExecuteSpecialProcessor is call below ExecuteProgram.  However, in the
 * case of a Windows prog, its called above, so we set this to stop
 * Executeprogram invoking the program again, but telling it that no
 * swapping was required.
 */

    ExecProcessingMode.Flags = EP_NOSWAP;
    return res;
}
#endif  /*!OS_UNIX*/


/*
 * Generic processing for script and windows programs
 */
static int F_LOCAL
ExecuteSpecialProcessor (
    char *Fullpath, char **argv, char **envp, int ForkAction, Word_B *wb )
{
    char        **nargv;
    char        *p_name;                /* Program name         */
    char        *cp;
    int         j;

/* Add the rest of the parameters */

    wb = AddWordToBlock (Fullpath, wb);

    j = 1;
    while (argv[j] != NOWORD)
        wb = AddWordToBlock (argv[j++], wb);

/* Execute the program */

    nargv = GetWordList (AddWordToBlock (NOWORD, wb));

/* Special for UNIX compatability, use ourselves */

    if ((strcmp (nargv[0], "/bin/sh") == 0) ||
        (strcmp (nargv[0], "/bin/ksh") == 0))
        nargv[0] = GetVariableAsString (ShellVariableName, FALSE);

/* See if the program exists.  If it doesn't, strip the path */

    else if (((cp = strrchr (nargv[0], CHAR_UNIX_DIRECTORY)) != (char *)NULL) &&
             ((p_name = AllocateMemoryCell (FFNAME_MAX)) != (char *)NULL))
    {
        if (FindLocationOfExecutable (p_name, nargv[0]) == EXTENSION_NOT_FOUND)
            strcpy (nargv[0], cp + 1);

        ReleaseMemoryCell (p_name);
    }

/* Get the new program mode */

    CheckProgramMode (*nargv, &ExecProcessingMode);

    j = EnvironExecute (nargv, ForkAction);

    if (ExecProcessingMode.Flags != EP_ENVIRON)
        j = LocalExecve (nargv, envp, ForkAction);

/* 0 is a special case - see ConvertErrorNumber */

    if (j == -1)
        errno = 0;

    return j;
}


/*
 * Parse a new CLI string
 */
static bool F_LOCAL
SetUpCLI (char *string, Word_B **wb)
{
    char        *sp = StringCopy (string);

    if (sp == null)
        return FALSE;

    *wb = SplitString (sp, *wb);
    return TRUE;
}


static void
PrintProgramMode ( ExeMode *PMode )
{
    if (PMode->Flags & EP_DOSMODE)   fprintf (stderr, "DOSMODE ");
    if (PMode->Flags & EP_UNIXMODE)  fprintf (stderr, "UNIXMODE ");
    if (PMode->Flags & EP_NOEXPAND)  fprintf (stderr, "NOEXPAND ");
    if (PMode->Flags & EP_ENVIRON)   fprintf (stderr, "ENVIRON ");
    if (PMode->Flags & EP_NOSWAP)    fprintf (stderr, "NOSWAP ");
    if (PMode->Flags & EP_QUOTEWILD) fprintf (stderr, "QUOTEWILD ");
    if (PMode->Flags & EP_EXPORT)    fprintf (stderr, "EXPORT ");
    if (PMode->Flags & EP_CONVERT)   fprintf (stderr, "CONVERT ");
    if (PMode->Flags & EP_NOWORDS)   fprintf (stderr, "NOWORDS ");
    if (PMode->Flags & EP_NOQUOTE)   fprintf (stderr, "NOQUOTE ");
    if (PMode->Flags & EP_IGNTYPE)   fprintf (stderr, "IGNTYPE ");
    if (PMode->Flags & EP_PSEUDOTTY) fprintf (stderr, "PSEUDOTTY ");
    if (PMode->Flags & EP_DOSEXT)    fprintf (stderr, "DOSEXT ");
    fprintf (stderr, "\n");
}


#if (OS_TYPE == OS_DOS)
/*
 * Check the program type
 */
void
CheckProgramMode (
    char *Pname, ExeMode *PMode )
{
    char                *pEnv;
    int                 builtin;                /* Builtin function     */
    ExecMode_t          mode;

/* Check for internal no-globbed commands */

    if ((IsCommandBuiltIn (Pname, &builtin) != (int (*)(int, char **))NULL) &&
        ((builtin & BLT_SKIPGLOB) == BLT_SKIPGLOB))
    {
        PMode->Flags = EP_NOEXPAND | ((builtin & BLT_NOWORDS) ? EP_NOWORDS : 0);
        return;
    }

/* Set not found */

    mode.Flags = EM_NONE;
    mode.Name = (char *)NULL;

/* Check not a function */

    if ((Pname != (char *)NULL) &&
           ((pEnv = GetVariableAsString("EXTENDED_LINE", FALSE)) != null))
    {
        if (FL_TEST(FLAG_DEBUG_EXECUTE))
            __systeml_debug = 1;
        __systeml_mode(pEnv, Pname, &mode);     /* Read extend config */
    }

/* Copy results */

    PMode->Name = mode.Name;
    PMode->Flags = EP_NONE;
    if (mode.Flags & EM_DOSMODE)   PMode->Flags |= EP_DOSMODE;
    if (mode.Flags & EM_UNIXMODE)  PMode->Flags |= EP_UNIXMODE;
    if (mode.Flags & EM_NOEXPAND)  PMode->Flags |= EP_NOEXPAND;
    if (mode.Flags & EM_ENVIRON)   PMode->Flags |= EP_ENVIRON;
    if (mode.Flags & EM_NOSWAP)    PMode->Flags |= EP_NOSWAP;
    if (mode.Flags & EM_QUOTEWILD) PMode->Flags |= EP_QUOTEWILD;
    if (mode.Flags & EM_EXPORT)    PMode->Flags |= EP_EXPORT;
    if (mode.Flags & EM_CONVERT)   PMode->Flags |= EP_CONVERT;
    if (mode.Flags & EM_NOWORDS)   PMode->Flags |= EP_NOWORDS;
    if (mode.Flags & EM_NOQUOTE)   PMode->Flags |= EP_NOQUOTE;
    if (mode.Flags & EM_IGNTYPE)   PMode->Flags |= EP_IGNTYPE;
    if (mode.Flags & EM_PSEUDOTTY) PMode->Flags |= EP_PSEUDOTTY;
    if (mode.Flags & EM_DOSEXT)    PMode->Flags |= EP_DOSEXT;
    PMode->FieldSep = mode.FieldSep;
}


#else   /*!OS_DOS*/

/*
 * Common fields in EXTENDED_LINE file
 */
#define COMMON_FIELD_COUNT      ARRAY_SIZE (CommonFields)

static struct CommonFields {
    char                *Name;
    unsigned int        Flag;
} CommonFields [] = {
    { "switch",         EP_CONVERT },
    { LIT_export,       EP_EXPORT },
    { "noswap",         EP_NOSWAP },
    { "noexpand",       EP_NOEXPAND },
    { "noquote",        EP_NOQUOTE },
    { "ignoretype",     EP_IGNTYPE },
    { "pipetty",        EP_PSEUDOTTY },
    { "quotewild",      EP_QUOTEWILD },
    { "dosext",         EP_DOSEXT },
};


/*
 * Check the program type
 */
void CheckProgramMode (char *Pname, ExeMode *PMode)
{
#if defined(USE_STUPID_BROKEN_CACHE)
    static struct {                     /* Single level Cache */
        char            *name;
        ExeMode         mode;
    } cache = { NULL, 0 };
#endif
    char                *sp, *sp1;      /* Line pointers        */
    int                 nFields;
    char                *SPname;
    int                 builtin;        /* Builtin function     */
    LineFields          LF;
    long                value;

/* Check for internal no-globbed commands */

    if ((IsCommandBuiltIn (Pname, &builtin) != (int (*)(int, char **))NULL) &&
        ((builtin & BLT_SKIPGLOB) == BLT_SKIPGLOB))
    {
        PMode->Flags = EP_NOEXPAND | ((builtin & BLT_NOWORDS) ? EP_NOWORDS : 0);
        return;
    }

/* Set not found */

    PMode->Flags = EP_NONE;
    PMode->Name = NULL;

/* Check not a function */

    if ((Pname == (char *)NULL) ||
                ((sp = GetVariableAsString ("EXTENDED_LINE", FALSE)) == null))
        return;

/* Remove terminating .exe etc */

    sp1 = ((sp1 = FindLastPathCharacter (Pname)) == (char *)NULL)
                 ? Pname : sp1 + 1;

    if (IsDriveCharacter (*(sp1 + 1)))
        sp1 += 2;

    if ((SPname = StringCopy (sp1)) == null)
        return;

    if ((sp1 = strrchr (SPname, CHAR_PERIOD)) != (char *)NULL)
        *sp1 = 0;

#if defined(USE_STUPID_BROKEN_CACHE)
/* Check cache (26/09/00) */
    if (cache.name)
    {
       if (! NOCASE_COMPARE (cache.name, SPname))
       {
            PMode->Flags |= (cache.mode.Flags & EP_CACHED);
            if (cache.mode.Name)
               PMode->Name = StringCopy(cache.mode.Name);
            else PMode->Name = NULL;
            PMode->FieldSep = cache.mode.FieldSep;
            ReleaseMemoryCell ((void *)SPname);
            return;
       }
    }
#endif

/* Open the file */

    if ((LF.Line = AllocateMemoryCell (LF.LineLength = 200)) == (char *)NULL)
    {
        ReleaseMemoryCell ((void *)SPname);
        return;
    }

    if ((LF.FP = FOpenFile (sp, sOpenReadMode)) == (FILE *)NULL)
    {
        ReleaseMemoryCell ((void *)LF.Line);
        ReleaseMemoryCell ((void *)SPname);
        return;
    }

/* Initialise the internal buffer */

    LF.Fields = (Word_B *)NULL;

/* Scan for the file name */

    while ((nFields = ExtractFieldsFromLine (&LF)) != -1)
    {
        if (nFields < 2)
            continue;

/* Remove terminating .exe etc */

#if (OS_TYPE != OS_UNIX)
        if ((sp = strrchr (LF.Fields->w_words[0], CHAR_PERIOD)) != (char *)NULL)
            *sp = 0;
#endif

        if (NOCASE_COMPARE (LF.Fields->w_words[0], SPname))
            continue;

/* What type? */

        if (NOCASE_COMPARE (LF.Fields->w_words[1], "unix") == 0)
            PMode->Flags = (unsigned int )(EP_UNIXMODE |
                                CheckForCommonOptions (&LF, 2));

        else if (NOCASE_COMPARE (LF.Fields->w_words[1], LIT_dos) == 0)
            PMode->Flags = (unsigned int )(EP_DOSMODE |
                                CheckForCommonOptions (&LF, 2));

/* Must have a valid name and we can get memory for it */

        else if ((NOCASE_COMPARE (LF.Fields->w_words[1], "environ") == 0) &&
                 (nFields >= 3) &&
                 (!IsValidVariableName (LF.Fields->w_words[2])) &&
                 ((PMode->Name =
                     StringCopy (LF.Fields->w_words[2])) != null))
        {
            PMode->Flags = EP_ENVIRON;
            PMode->FieldSep = 0;

            if ((nFields >= 4) &&
                ConvertNumericValue (LF.Fields->w_words[3], &value, 0))
                PMode->FieldSep = (unsigned char)value;

            if (!PMode->FieldSep)
                PMode->FieldSep = CHAR_SPACE;
        }

        else
            PMode->Flags = CheckForCommonOptions (&LF, 1);

        break;
    }

    CloseFile (LF.FP);
    ReleaseMemoryCell ((void *)LF.Line);

#if defined(USE_STUPID_BROKEN_CACHE)
/* Update cache (26/09/00) */
    if (cache.name)                     /* program name */
        ReleaseMemoryCell (cache.name);
    cache.name = SPname;

    cache.mode.Flags = PMode->Flags;    /* exec mode */
    if (cache.mode.Name)
        ReleaseMemoryCell ((void *)cache.mode.Name);

    if ((PMode->Flags & EP_ENVIRON) && PMode->Name)
        cache.mode.Name = StringCopy(PMode->Name);
    else
        cache.mode.Name = NULL;
    cache.mode.FieldSep = PMode->FieldSep;
#endif
}


/*
 * Check for common fields
 */
static unsigned int F_LOCAL
CheckForCommonOptions(
    LineFields *LF, int Start )
{
    unsigned int        Flags = 0;
    int                 i, j;

    if (LF->Fields == (Word_B *)NULL)
        return 0;

    for (i = Start; i < LF->Fields->w_nword; i++)
    {
        for (j = 0; j < COMMON_FIELD_COUNT; ++j)
        {
            if (!NOCASE_COMPARE (LF->Fields->w_words[i], CommonFields[j].Name))
            {
                Flags |= CommonFields[j].Flag;
                break;
            }
        }
    }

    return Flags;
}
#endif  /*OS_DOS*/