Subversion Repositories DevTools

Rev

Blame | Last modification | View Log | RSS feed

/*
 * MS-DOS SHELL - Lexical Scanner
 *
 * MS-DOS SHELL - Copyright (c) 1990,4 Data Logic Limited and Charles Forsyth
 *
 * This code is based on (in part) the shell program written by Charles
 * Forsyth and the subsequence modifications made by Simon J. Gerraty (for
 * his Public Domain Korn Shell) 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.
 *
 *    $Header: /cvsroot/device/DEVL/UTILS/SH/SH5.C,v 1.2 2004/05/10 09:30:06 ayoung Exp $
 *
 *    $Log: SH5.C,v $
 *    Revision 1.2  2004/05/10 09:30:06  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:33  adamy
 *    imported (reference only)
 *
 *    Revision 1.1  2001/07/20 05:55:42  ayoung
 *    WIN32 support
 *
 *    Revision 1.1.1.1  1999/12/02 01:11:12  gordonh
 *    UTIL
 *
 *      Revision 2.13  1994/08/25  20:49:11  istewart
 *      MS Shell 2.3 Release
 *
 *      Revision 2.12  1994/02/01  10:25:20  istewart
 *      Release 2.3 Beta 2, including first NT port
 *
 *      Revision 2.11  1994/01/11  17:55:25  istewart
 *      Release 2.3 Beta 0 patches
 *
 *      Revision 2.10  1993/11/09  10:39:49  istewart
 *      Beta 226 checking
 *
 *      Revision 2.9  1993/08/25  16:03:57  istewart
 *      Beta 225 - see Notes file
 *
 *      Revision 2.8  1993/07/02  10:21:35  istewart
 *      224 Beta fixes
 *
 *      Revision 2.7  1993/06/14  11:00:12  istewart
 *      More changes for 223 beta
 *
 *      Revision 2.6  1993/06/02  09:52:35  istewart
 *      Beta 223 Updates - see Notes file
 *
 *      Revision 2.5  1993/02/16  16:03:15  istewart
 *      Beta 2.22 Release
 *
 *      Revision 2.4  1993/01/26  18:35:09  istewart
 *      Release 2.2 beta 0
 *
 *      Revision 2.3  1992/11/06  10:03:44  istewart
 *      214 Beta test updates
 *
 *      Revision 2.2  1992/09/03  18:54:45  istewart
 *      Beta 213 Updates
 *
 *      Revision 2.1  1992/07/10  10:52:48  istewart
 *      211 Beta updates
 *
 *      Revision 2.0  1992/04/13  17:39:09  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 <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <dirent.h>
#include <unistd.h>
#include "sh.h"

/*
 * lexical analysis and source input
 */

/* we set s->str to NULLSTR instead of "", so that ungetsc() works */

static  char    nullstr [] = {0, 0};
#define NULLSTR (nullstr + 1)

static bool             expanding_alias = FALSE;
static bool             AllowMultipleAliases = FALSE;

/*
 * Here document processing
 */

typedef struct here {
    struct here         *h_next;        /* Link to next                 */
    IO_Actions          *h_iop;         /* I/O options                  */
} Here_D;

/*
 * The full list contains all the documents created during a parse.  The
 * active list, contains the non-processed ones.
 */

                                        /* Full list header             */
static Here_D           *HereListHead = (Here_D *)NULL;
                                        /* Active list Header           */
static Here_D           *ActiveListHead = (Here_D *)NULL;


static void F_LOCAL     ReadHereDocument (IO_Actions *iop);
static void F_LOCAL     GetHereDocuments (void);

static char             *LIT_Unclosed = "here document `%s' unclosed";

/*
 * Get next character functions
 */

static int F_LOCAL      getsc_ (void);

/* optimized getsc_() */

#define getsc()         ((*source->str != 0) ? (int)*(source->str++) : getsc_())
#define ungetsc()       (source->str--)

/*
 * states while lexing word
 */

#define SBASE           0        /* outside any lexical constructs       */
#define SSQUOTE         1       /* inside ''                            */
#define SDQUOTE         2       /* inside ""                            */
#define SBRACE          3       /* inside ${}                           */
#define SPAREN          4       /* inside $()                           */
#define SBQUOTE         5       /* inside ``                            */
#define SWORD           6       /* implicit quoting for substitute()    */
#define SDPAREN         7       /* inside (( )), implicit quoting       */
#define SDDPAREN        8       /* inside $(( )), implicit quoting      */
#define SVSUBST         9       /* inside ${ }, following the V name    */
#define SVARRAY         10      /* inside ${name [..].. }               */

/*
 * Lexical analyzer
 *
 * tokens are not regular expressions, they are LL(1).
 * for example, "${var:-${PWD}}", and "$(size $(whence ksh))".
 * hence the state stack.
 */

int     ScanNextToken (int LexicalControlFlags)
{
    int                 c;
    char                states [64], *statep = states;
    XString             ws;             /* expandable output word       */
    unsigned char       *wp;            /* output word pointer          */
    char                *sp, *dp;
    char                istate;
    char                state;
    int                 c2 = 0;
    static int          RecursiveAliasCount = 0;
    static AliasList    *RecursiveAlias[MAX_RECURSIVEALIASES];
    int                 SDD_Count = 0;  /* Parathensis counter          */
    char                LexSeparator;

    if (expanding_alias)
    {
        expanding_alias = FALSE;

        while (RecursiveAliasCount-- > 0)
            RecursiveAlias[RecursiveAliasCount]->AFlags &= ~ALIAS_EXPANDING;

        RecursiveAliasCount = 0;
    }

/*
 * Loop round again!
 */

Again:

    wp = (unsigned char *)XCreate (&ws, 64);

/*
 * Single word ?
 */

    if (LexicalControlFlags & ONEWORD)
        istate = SWORD;

/*
 * Maths expression?
 */

    else if (LexicalControlFlags & MATHS_EXPRESSION)
    {
        istate = SDPAREN;
        *(wp)++ = WORD_ODQUOTE;
    }

/*
 * Normal scanning
 */

    else
    {
        istate = SBASE;

/* Ignore white space */

        while ((c = getsc ()) == CHAR_SPACE || c == CHAR_TAB)
            continue;

/* Comment? */

        if (c == CHAR_COMMENT)
        {
            while (((c = getsc ()) != 0) && (c != CHAR_NEW_LINE))
                continue;
        }

        ungetsc ();
    }

/*
 * Which separator test to use?
 */

    LexSeparator = (char)((LexicalControlFlags & TEST_EXPRESSION)
                        ? (C_IFS | C_SEMICOLON)
                        : C_LEX1);

/*
 * trailing ' ' in alias definition - Yes, allow more aliases
 */

    if (AllowMultipleAliases)
    {
        AllowMultipleAliases = FALSE;
        LexicalControlFlags |= ALLOW_ALIAS;
    }

/*
 * collect non-special or quoted characters to form word
 */

    for (*statep = state = istate;
         !(((c = getsc ()) == 0) ||
           ((state == SBASE) && (CharTypes[c] & LexSeparator))); )
    {
        XCheck (&ws, &wp);

        switch (state)
        {
            case SBASE:
BasicCharacterProcessing:
                switch (c)
                {
                    case CHAR_META:
                        c = getsc ();

                        if (c != CHAR_NEW_LINE)
                        {
                            *(wp)++ = WORD_QTCHAR;
                            *(wp)++ = (unsigned char)c;
                        }

                        else if (wp == XStart (ws))
                            goto Again;

                        break;

                    case CHAR_SINGLE_QUOTE:
                        *++statep = state = SSQUOTE;
                        *(wp)++ = WORD_OQUOTE;
                        break;

                    case CHAR_DOUBLE_QUOTE:
                        *++statep = state = SDQUOTE;
                        *(wp)++ = WORD_ODQUOTE;
                        break;

                    default:
                        goto Subst;
                }

                break;

/* Non-special character processing */

Subst:
                switch (c)
                {

/*
 * Escaped character
 */
                    case CHAR_META:
                        switch (c = getsc ())
                        {
                            case CHAR_NEW_LINE:
                                break;

                            case CHAR_DOUBLE_QUOTE:
                            case CHAR_META:
                            case CHAR_VARIABLE:
                            case CHAR_BACKQUOTE:
                                *(wp)++ = WORD_QTCHAR;
                                *(wp)++ = (unsigned char)c;
                                break;

                            default:
                                XCheck (&ws, &wp);
                                *(wp)++ = WORD_CHAR;
                                *(wp)++ = CHAR_META;
                                *(wp)++ = WORD_CHAR;
                                *(wp)++ = (unsigned char)c;
                                break;
                        }

                        break;

/*
 * Handler $(), $(()), ${}, $var, $num, $#, $*, $@
 */

                    case CHAR_VARIABLE:
                        c = getsc ();

/* Check for $() & $(()) */

                        if (c == CHAR_OPEN_PARATHENSIS)
                        {
                            if (getsc () == CHAR_OPEN_PARATHENSIS)
                            {
                                *++statep = state = SDDPAREN;
                                *(wp)++ = WORD_OMATHS;
                                SDD_Count = 0;
                            }

                            else
                            {
                                ungetsc ();
                                *++statep = state = SPAREN;
                                *(wp)++ = WORD_COMSUB;
                            }
                        }

/* Check for ${} */

                        else if (c == CHAR_OPEN_BRACES)
                        {
                            *++statep = state = SVSUBST;
                            *(wp)++ = WORD_OSUBST;
                            c = getsc ();

                            if (!IS_VariableFC (c) && !IS_VarNumeric (c))
                                ShellErrorMessage ("invalid variable name");

                            do
                            {
                                XCheck (&ws, &wp);
                                *(wp)++ = (unsigned char)c;
                                c = getsc ();
                            } while (IS_VariableSC (c));

                            *(wp)++ = 0;
                            ungetsc ();
                        }

/* Check for $var */

                        else if (IS_VariableFC (c))
                        {
                            *(wp)++ = WORD_OSUBST;

                            do
                            {
                                XCheck (&ws, &wp);
                                *(wp)++ = (unsigned char)c;
                                c = getsc ();
                            } while (IS_VariableSC(c));

                            *(wp)++ = 0;
                            *(wp)++ = WORD_CSUBST;
                            ungetsc ();

                        }

/* Check for $number, $*, $@, $#, $! $$ $- $? */

                        else if (IS_VarNumeric (c))
                        {
                            XCheck (&ws, &wp);
                            *(wp)++ = WORD_OSUBST;
                            *(wp)++ = (unsigned char)c;
                            *(wp)++ = 0;
                            *(wp)++ = WORD_CSUBST;
                        }

/* $??? - no mapping */

                        else
                        {
                            *(wp)++ = WORD_CHAR;
                            *(wp)++ = CHAR_VARIABLE;
                            ungetsc ();
                        }

                        break;

/*
 *  Handler ` ` (old style $())
 */
                    case CHAR_BACKQUOTE:
                        *++statep = state = SBQUOTE;
                        *(wp)++ = WORD_COMSUB;
                        break;

/*
 * Normal character
 */

                    default:
                        *(wp)++ = WORD_CHAR;
                        *(wp)++ = (unsigned char)c;
                }

                break;

            case SSQUOTE:                       /* Inside '...'         */
                if (c == CHAR_SINGLE_QUOTE)
                {
                    state = *--statep;
                    *(wp)++ = WORD_CQUOTE;
                }

/* Check for an escaped '.  None of the UNIX versions seem to do this, so
 * it may be wrong, so I've #if it out
 */

                else
                {
                    *(wp)++ = WORD_QCHAR;
                    *(wp)++ = (unsigned char)c;
                }

#if 0
                else if (c != CHAR_META)
                {
                    *(wp)++ = WORD_QCHAR;
                    *(wp)++ = (unsigned char)c;
                }

/* Yes - insert a quoted ' */
                else if ((c = getsc ()) == CHAR_SINGLE_QUOTE)
                {
                    *(wp)++ = WORD_QTCHAR;
                    *(wp)++ = CHAR_SINGLE_QUOTE;
                }

/* No - unget the character and insert the meta */

                else
                {
                    ungetsc ();
                    *(wp)++ = WORD_QCHAR;
                    *(wp)++ = CHAR_META;
                }
#endif

                break;

            case SDQUOTE:                       /* Inside "..."         */
                if (c == CHAR_DOUBLE_QUOTE)
                {
                    state = *--statep;
                    *(wp)++ = WORD_CDQUOTE;
                }

                else
                    goto Subst;

                break;

            case SPAREN:                        /* Inside $(...)        */
                if (c == CHAR_OPEN_PARATHENSIS)
                    *++statep = state;

                else if (c == CHAR_CLOSE_PARATHENSIS)
                    state = *--statep;

                if (state == SPAREN)
                    *(wp)++ = (unsigned char)c;

                else
                    *(wp)++ = 0; /* end of WORD_COMSUB */

                break;

            case SBRACE:                        /* Inside ${...}        */
                if (c != CHAR_CLOSE_BRACES)
                    goto BasicCharacterProcessing;

                state = *--statep;
                *(wp)++ = WORD_CSUBST;
                break;

            case SVARRAY:                       /* Inside ${name [...].}*/
                if (c != CHAR_CLOSE_BRACKETS)
                    goto BasicCharacterProcessing;

                state = *--statep;
                *(wp)++ = WORD_CARRAY;
                break;


            case SVSUBST:               /* Inside ${ }, following the   */                                              /* Variable name                */

/* Set state to SBRACE to handle closing brace. */

                *statep = state = SBRACE;

/* Simple Name
 *
 * Possibilities: ${name}
 */

                if (c == CHAR_CLOSE_BRACES)
                    ungetsc ();

/*
 * Array Name.  Reset to this state for closing ] post processing
 *
 * Possibilities: ${name[index].....}
 */

                else if (c == CHAR_OPEN_BRACKETS)
                {
                    *statep = SVSUBST;
                    *++statep = state = SVARRAY;
                    *(wp)++ = WORD_OARRAY;
                }

/* Korn pattern trimming
 *
 * Possibilities: ${name#string}
 *                ${name##string}
 *                ${name%string}
 */

                else if ((c == CHAR_MATCH_START) || (c == CHAR_MATCH_END))
                {
                    if (getsc () == c)
                        c |= CHAR_MAGIC;

                    else
                        ungetsc ();

                    *(wp)++ = (unsigned char)c;
                }

/* Subsitution
 *
 * Possibilities: ${name:?value}
 *                ${name:-value}
 *                ${name:=value}
 *                ${name:+value}
 *                ${name?value}
 *                ${name-value}
 *                ${name=value}
 *                ${name+value}
 */

                else
                {
                    c2 = (c == ':') ? CHAR_MAGIC : 0;

                    if (c == ':')
                        c = getsc();

                    if (!IS_VarOp (c))
                    {
                        CompilingError ();
                        ShellErrorMessage ("Unknown substitution operator '%c'",
                                           c);
                    }

                    *(wp)++ = (unsigned char)(c | c2);
                }

                break;

            case SBQUOTE:                       /* Inside `...`         */
                if (c == CHAR_BACKQUOTE)
                {
                    *(wp)++ = 0;
                    state = *--statep;
                }

                else if (c == CHAR_META)
                {
                    switch (c = getsc())
                    {
                        case CHAR_NEW_LINE:
                            break;

                        case CHAR_META:
                        case CHAR_VARIABLE:
                        case CHAR_BACKQUOTE:
                            *(wp)++ = (unsigned char)c;
                            break;

                        default:
                            *(wp)++ = CHAR_META;
                            *(wp)++ = (unsigned char)c;
                            break;
                    }
                }

                else
                    *(wp)++ = (unsigned char)c;

                break;

            case SWORD:                         /* ONEWORD              */
                goto Subst;

            case SDPAREN:                       /* Include ((....))     */
                if (c == CHAR_CLOSE_PARATHENSIS)
                {
                    if (getsc () == CHAR_CLOSE_PARATHENSIS)
                    {
                        *(wp)++ = WORD_ODQUOTE;
                        c = 0;
                        goto Done;
                    }

                    else
                        ungetsc ();
                }

                goto Subst;

            case SDDPAREN:                      /* Include $((....))    */

/* Check for sub-expressions */

                if (c == CHAR_OPEN_PARATHENSIS)
                    SDD_Count++;

/* Check for end of sub-expression or $((..)) */

                else if (c == CHAR_CLOSE_PARATHENSIS)
                {
                    if (SDD_Count)
                        SDD_Count--;

                    else if (getsc () == CHAR_CLOSE_PARATHENSIS)
                    {
                        state = *--statep;
                        *(wp)++ = 0;
                        break;
                    }

                    else
                    {
                        CompilingError ();
                        ShellErrorMessage ("maths sub-expression not closed");
                    }
                }

/* Normal character? */

                *(wp)++ = (char)c;
                break;
        }
    }

/* Finally, the end! */

Done:
    XCheck (&ws, &wp);

    if (state != istate)
    {
        CompilingError ();
        ShellErrorMessage ("no closing quote");
    }

    if (!(LexicalControlFlags & TEST_EXPRESSION) &&
         ((c == CHAR_INPUT) || (c == CHAR_OUTPUT)))
    {
        unsigned char *cp = XStart (ws);

/* Set c2 to the io unit value */

        if ((wp > cp) && (cp[0] == WORD_CHAR) && (IS_Numeric (cp[1])))
        {
            wp = cp; /* throw away word */
            c2 = cp[1] - '0';
        }

        else
            c2 = (c == CHAR_OUTPUT) ? 1 : 0;    /* 0 for <, 1 for > */
    }

/* no word, process LEX1 character */

    if ((wp == XStart (ws)) && (state == SBASE))
    {
        XFree (ws);     /* free word */
        
        switch (c)
        {
            default:
                return c;

    /* Check for double character thingys - ||, &&, ;; */

            case CHAR_PIPE:
            case CHAR_ASYNC:
            case CHAR_SEPARATOR:
                if ((c2 = getsc ()) == c)
                {
                    switch (c)
                    {
                        case CHAR_PIPE:
                            c = PARSE_LOGICAL_OR;
                            break;

                        case CHAR_ASYNC:
                            c = PARSE_LOGICAL_AND;
                            break;

                        case CHAR_SEPARATOR:
                            c = PARSE_BREAK;
                            break;

                        default:
                            c = YYERRCODE;
                            break;
                    }
                }

/* Check for co-process |& */

                else if ((c == CHAR_PIPE) && (c2 == CHAR_ASYNC))
                    c = PARSE_COPROCESS;

                else
                    ungetsc ();

                return c;

    /* Re-direction */

            case CHAR_INPUT:
            case CHAR_OUTPUT:
            {
                IO_Actions      *iop;

                iop = (IO_Actions *) GetAllocatedSpace (sizeof (IO_Actions));
                iop->io_unit = c2/*unit*/;

    /* Look at the next character */

                c2 = getsc ();

    /* Check for >>, << & <> */

                if ((c2 == CHAR_OUTPUT) || (c2 == CHAR_INPUT) )
                {
                    iop->io_flag = (c != c2) ? IORDWR
                                             : (c == CHAR_OUTPUT) ? IOCAT
                                                                  : IOHERE;
                    c2 = getsc ();
                }

                else
                    iop->io_flag = (c == CHAR_OUTPUT) ? IOWRITE : IOREAD;

    /* Check for <<- - strip tabs */

                if (iop->io_flag == IOHERE)
                {
                    if (c2 == '-')
                        iop->io_flag |= IOSKIP;

                    else
                        ungetsc ();
                }

    /* Check for <& or >& */

                else if (c2 == '&')
                    iop->io_flag = IODUP;

    /* Check for >! */

                else if ((c2 == CHAR_PIPE) && (iop->io_flag == IOWRITE))
                    iop->io_flag |= IOCLOBBER;

                else
                    ungetsc ();

                yylval.iop = iop;
                return PARSE_REDIR;
            }

            case CHAR_NEW_LINE:
                GetHereDocuments ();

                if (LexicalControlFlags & ALLOW_CONTINUATION)
                    goto Again;

                return c;

            case CHAR_OPEN_PARATHENSIS:
                c2 = getsc();

                if (c2 == CHAR_CLOSE_PARATHENSIS)
                    c = PARSE_MPAREN;

                else if (c2 == CHAR_OPEN_PARATHENSIS)
                    c = PARSE_MDPAREN;

                else
                    ungetsc();

            case CHAR_CLOSE_PARATHENSIS:
                return c;
        }
    }

/* Must be a word !!! */

    *(wp)++ = WORD_EOS;         /* terminate word */
    yylval.cp = XClose (&ws, wp);

    if (state == SWORD || state == SDPAREN)     /* ONEWORD? */
        return PARSE_WORD;

    ungetsc ();         /* unget terminator */

/* Make sure the CurrentLexIdentifier array stays '\0' padded */

    memset (CurrentLexIdentifier, 0, IDENT);

/* copy word to unprefixed string CurrentLexIdentifier */

    for (sp = yylval.cp, dp = CurrentLexIdentifier;
         (dp < CurrentLexIdentifier + IDENT) && ((c = *sp++) == WORD_CHAR); )
            *dp++ = *sp++;

    if (c != WORD_EOS)
        *CurrentLexIdentifier = 0;      /* word is not unquoted */

/* Are we allowing Keywords and Aliases ? */

    if (*CurrentLexIdentifier != 0 &&
        (LexicalControlFlags & (ALLOW_KEYWORD | ALLOW_ALIAS)))
    {
        AliasList       *p;
        int             yval;
        Source          *s;

/* Check keyword */

        if ((LexicalControlFlags & ALLOW_KEYWORD) &&
            (yval = LookUpSymbol (CurrentLexIdentifier)))
        {
            ReleaseMemoryCell (yylval.cp);
            return yval;
        }

/* Check Alias */

        if ((LexicalControlFlags & ALLOW_ALIAS) &&
            ((p = LookUpAlias (CurrentLexIdentifier, TRUE)) !=
                 (AliasList *)NULL) &&
            (!(p->AFlags & ALIAS_EXPANDING)))
        {
            if (RecursiveAliasCount == MAX_RECURSIVEALIASES)
            {
                CompilingError ();
                ShellErrorMessage ("excessive recusrsive aliasing");
            }

            else
                RecursiveAlias[RecursiveAliasCount++] = p;

            p->AFlags |= ALIAS_EXPANDING;

/* check for recursive aliasing */

            for (s = source; s->type == SALIAS; s = s->next)
            {
                if (s->u.Calias == p)
                    return PARSE_WORD;
            }

            ReleaseMemoryCell ((void *)yylval.cp);

/* push alias expansion */

            s = pushs (SALIAS);
            s->str = p->value;
            s->u.Calias = p;
            s->next = source;
            source = s;
            goto Again;
        }
    }

    return PARSE_WORD;
}

/*
 * read "<<word" text into temp file
 */

static void F_LOCAL ReadHereDocument (struct ioword *iop)
{
    FILE        *FP = NULL;
    int         tf;
    int         c;
    char        *EoFString;
    char        *cp;
    char        *Line;
    size_t      LineLen;

/* Expand the terminator */

    LineLen = strlen (EoFString = ExpandAString (iop->io_name, 0));

/* Save the tempoary file information */

    iop->io_name = StringCopy (GenerateTemporaryFileName ());

/* Create the tempoary file */

    if (((tf = S_open (FALSE, iop->io_name, O_CMASK | O_NOINHERIT)) < 0)
        || ((FP = ReOpenFile (tf, sOpenWriteBinaryMode)) == (FILE *)NULL))
        ShellErrorMessage ("Cannot create temporary file");

/* Get some space */

    Line = GetAllocatedSpace (LineLen + 3);

/* Read the file */

    while (TRUE)
    {
        cp = Line;

/* Read the first n characters to check for terminator */

        while ((c = getsc ()) != CHAR_NEW_LINE)
        {
            if (c == 0)
                ShellErrorMessage (LIT_Unclosed, EoFString);

            if (cp > Line + LineLen)
                break;

/* Ignore leading tabs if appropriate */

            if (!((iop->io_flag & IOSKIP) && (cp == Line) && (c == CHAR_TAB)))
                *(cp++) = (char)c;
        }

        *cp = 0;
        ungetsc ();

/* Check for end of string */

        if (strcmp (EoFString, Line) == 0 || c == 0)
            break;

/* Dump the line */

        if (FP) fputs (Line, FP);

        while ((c = getsc ()) != CHAR_NEW_LINE)
        {
            if (c == 0)
                ShellErrorMessage (LIT_Unclosed, EoFString);

            if (FP) putc (c, FP);
        }

        if (FP) putc (c, FP);
    }

    if (FP) S_fclose (FP, TRUE);
}

/*
 * Handle scanning error
 */

void    CompilingError (void)
{
    yynerrs++;

    while (source->type == SALIAS) /* pop aliases */
        source = source->next;

    source->str = NULLSTR;      /* zap pending input */
}

/*
 * input for ScanNextToken with alias expansion
 */

Source *pushs (int type)
{
    Source *s = (Source *) GetAllocatedSpace (sizeof (Source));

    s->type = type;
    s->str = NULLSTR;
    return s;
}


/*
 * Get the next string from input source
 */

static int F_LOCAL getsc_ (void)
{
    Source      *s = source;
    int         c;

    while ((c = *s->str++) == 0)
    {
        s->str = NULL;          /* return 0 for EOF by default */

        switch (s->type)
        {
            case SEOF:
                s->str = NULLSTR;
                return 0;

            case STTY:
                s->line++;
                s->str = ConsoleLineBuffer;
                s->str[c = GetConsoleInput ()] = 0;

                FlushStreams ();

/* EOF? */
                if (c == 0)
                {
                    s->str = NULL;
                    s->line--;
                }

/* Ignore pre-white space */

                else
                {
                    c = 0;

                    while (s->str[c] && IS_IFS ((int)s->str[c]))
                        c++;

/* Blank line?? */

                    if (!s->str[c])
                    {
                        s->str = &s->str[c - 1];
                        s->line--;
                    }

                    else
                        s->str = &s->str[c];
                }

                break;

            case SFILE:
                s->line++;
                s->str = fgets (e.line, LINE_MAX, s->u.file);

                DPRINT (1, ("getsc_: File line = <%s>", s->str));

                if (s->str == NULL)
                {
                    if (s->u.file != stdin)
                        S_fclose (s->u.file, TRUE);
                }

                break;

            case SWSTR:
                break;

            case SSTRING:
                s->str = LIT_NewLine;
                s->type = SEOF;
                break;

            case SWORDS:
                s->str = *s->u.strv++;
                s->type = SWORDSEP;
                break;

            case SWORDSEP:
                if (*s->u.strv == NULL)
                {
                    s->str = LIT_NewLine;
                    s->type = SEOF;
                }

                else
                {
                    s->str = " ";
                    s->type = SWORDS;
                }

                break;

            case SALIAS:
                s->str = s->u.Calias->value;

/*
 * If there is a trailing ' ', allow multiple aliases
 */

                if ((*(s->str) != 0) &&
                    (((c = s->str[strlen (s->str) - 1]) == CHAR_SPACE) ||
                     (c == CHAR_TAB)))
                    AllowMultipleAliases = TRUE;

                source = s = s->next;
                expanding_alias = TRUE;
                continue;
        }

        if (s->str == (char *)NULL)
        {
            s->type = SEOF;
            s->str = NULLSTR;
            return 0;
        }

        if (s->echo)
            foputs (s->str);
    }

    return c;
}

/*
 * Close all file handlers
 */

void CloseAllHandlers (void)
{
    int         u;

/*
 * Close any stream IO blocks
 */

    for (u = 0; u < MaxNumberofFDs; u++)
    {
#if  defined (__TURBOC__)
        if ((_streams[u].flags & _F_RDWR) && (_streams[u].fd >= NUFILE))
            fclose (&_streams[u]);
#elif  defined (__WATCOMC__)
        if ((u < _NFILES) && (__iob[u]._flag & (_READ | _WRITE)) &&
            (__iob[u]._handle >= NUFILE))
            fclose (&__iob[u]);
#elif (OS_TYPE == OS_UNIX)
#  ifdef __STDC__
        if ((__iob[u]._flag & (_IOREAD | _IORW | _IOWRT)) &&
            (__iob[u]._file >= NUFILE))
            fclose (&__iob[u]);
#  else
        if ((_iob[u]._flag & (_IOREAD | _IORW | _IOWRT)) &&
            (_iob[u]._file >= NUFILE))
            fclose (&_iob[u]);
#  endif
#elif defined (__EMX__)
        if ((_streamv[u].flags & (_IOREAD | _IORW | _IOWRT)) &&
            (_streamv[u].handle >= NUFILE))
            fclose (&_streamv[u]);
#elif !defined (__OS2__)
        if ((_iob[u]._flag & (_IOREAD | _IORW | _IOWRT)) &&
            (_iob[u]._file >= NUFILE))
            fclose (&_iob[u]);
#endif
    }

/* Close any open files */

    for (u = NUFILE; u < MaxNumberofFDs;)
        S_close (u++, TRUE);
}


/*
 * remap fd into Shell's fd space
 */

int ReMapIOHandler (int fd)
{
    int         i;
    int         n_io = 0;
    int         map [NUFILE];
    int         o_fd = fd;

    if (fd < FDBASE)
    {
        do
        {
            map[n_io++] = fd;
            fd = dup (fd);

        } while ((fd >= 0) && (fd < FDBASE));

        for (i = 0; i < n_io; i++)
            close (map[i]);

/* Check we can map it */

        if (fd >= (32 + FDBASE))
        {
            close (fd);
            fd = -1;
        }
        
        else
        {
            S_Remap (o_fd, fd);
            S_close (o_fd, TRUE);
        }

        if (fd < 0)
        {
#ifdef DEBUG
            struct stat         st;

            fprintf (stderr, "\nOld Fd=%d, New=%d, Map=0x%.8lx\n", o_fd, fd,
                     e.IOMap);

            feputs ("Ch. Device Inode  Mode  Links RDev   Size\n");

            for (i = 0; i < MaxNumberofFDs; i++)
            {
                if (fstat (i, &st) < 0)
                    fprintf (stderr, "%2d: cannot get status (%s)\n", i,
                             strerror (errno));

                else
                    fprintf (stderr, "%2d: 0x%.4x %5u %6o %5d 0x%.4x %ld\n", i,
                             st.st_dev, st.st_ino, st.st_mode, st.st_nlink,
                             st.st_rdev, st.st_size);
            }
#endif
            ShellErrorMessage ("too many files open");
        }
    }

    return fd;
}


/*
 * Generate a temporary filename
 */

char *GenerateTemporaryFileName (void)
{
    static char tmpfile[FFNAME_MAX];
    char        *tmpdir;        /* Points to directory prefix of pipe   */
    static int  temp_count = 0;
    char        *sep = DirectorySeparator;

/* Find out where we should put temporary files */

    if (((tmpdir = GetVariableAsString ("TMP", FALSE)) == null) &&
        ((tmpdir = GetVariableAsString ( HomeVariableName, FALSE )) == null) &&
        ((tmpdir = GetVariableAsString ("TMPDIR", FALSE)) == null)) 
    {
        static int first = 0;

        if (!first) {
            PrintWarningMessage (
               "sh: neither TMP nor TMPDIR are set, using current directory." );
            first = 1;  
        }
        tmpdir = CurrentDirLiteral;
    }

    if (strchr ("/\\", tmpdir[strlen (tmpdir) - 1]) != (char *)NULL)
        sep = null;

/* Get a unique temporary file name */

    while (1)
    {
        sprintf (tmpfile, "%s%ssht%.5u.tmp", tmpdir, sep, temp_count++);

        if (!S_access (tmpfile, F_OK))
            break;
    }

    return tmpfile;
}

/*
 * XString functions
 */

/*
 * Check an expandable string for overflow
 */

void    XCheck (XString *xs, unsigned char **xp)
{
    if (*xp >= xs->SEnd)
    {
        int     OldOffset = *xp - xs->SStart;

        xs->SStart = ReAllocateSpace (xs->SStart, (xs->SLength *= 2) + 8);
        xs->SEnd = xs->SStart + xs->SLength;
        *xp = xs->SStart + OldOffset;
    }
}

/*
 * Close an Expanded String
 */

char    *XClose (XString *xs, unsigned char *End)
{
    size_t      len = End - xs->SStart;
    char        *s = memcpy (GetAllocatedSpace (len), xs->SStart, len);

    ReleaseMemoryCell (xs->SStart);
    return s;
}

/*
 * Create an Expanded String
 */

char    *XCreate (XString *xs, size_t len)
{
    xs->SLength = len;
    xs->SStart = GetAllocatedSpace (len + 8);
    xs->SEnd = xs->SStart + len;
    return (char *)xs->SStart;
}

/*
 * here documents
 *
 * Save a here file's IOP for later processing (ie delete of the temp file
 * name)
 */

void SaveHereDocumentInfo (IO_Actions *iop)
{
    Here_D      *h, *lh;

    if ((h = (Here_D *) GetAllocatedSpace (sizeof(Here_D))) == (Here_D *)NULL)
        return;

    h->h_iop     = iop;
    h->h_next    = (Here_D *)NULL;

    if (HereListHead == (Here_D *)NULL)
        HereListHead = h;

    else
    {
        for (lh = HereListHead; lh != (Here_D *)NULL; lh = lh->h_next)
        {
            if (lh->h_next == (Here_D *)NULL)
            {
                lh->h_next = h;
                break;
            }
        }
    }
}

/*
 * Read all the active here documents
 */

static void F_LOCAL GetHereDocuments (void)
{
    Here_D      *h, *hp;

/* Scan here files first leaving HereListHead list in place */

    for (hp = h = HereListHead; h != (Here_D *)NULL; hp = h, h = h->h_next)
        ReadHereDocument (h->h_iop);

/* Make HereListHead list active - keep list intact for scraphere */

    if (hp != (Here_D *)NULL)
    {
        hp->h_next       = ActiveListHead;
        ActiveListHead   = HereListHead;
        HereListHead     = (Here_D *)NULL;
    }
}

/*
 * Zap all the here documents, unless they are currently in use by a
 * function.
 */

void ScrapHereList (void)
{
    Here_D      *h;

    for (h = HereListHead; h != (Here_D *)NULL; h = h->h_next)
    {
        if ((h->h_iop != (IO_Actions *)NULL) &&
            (h->h_iop->io_name != (char *)NULL) &&
            (!(h->h_iop->io_flag & IOFUNCTION)))
            unlink (h->h_iop->io_name);
    }

    HereListHead = (Here_D *)NULL;
}

/*
 * unlink here temp files before a ReleaseMemoryArea (area)
 */

void FreeAllHereDocuments (int area)
{
    Here_D      *current = ActiveListHead;
    Here_D      *previous = (Here_D *)NULL;

    while (current != (Here_D *)NULL)
    {
        if (GetMemoryAreaNumber ((void *)current) >= area)
        {
            if ((current->h_iop->io_name != (char *)NULL) &&
                (!(current->h_iop->io_flag & IOFUNCTION)))
                unlink (current->h_iop->io_name);

            if (ActiveListHead == current)
                ActiveListHead = current->h_next;

            else
                previous->h_next = current->h_next;
        }

        previous = current;
        current = current->h_next;
    }
}

/*
 * Open here document temp file.
 * If unquoted here, expand here temp file into second temp file.
 */

int     OpenHereFile (char *hname, bool sub)
{
    FILE        *FP;
    char        *cp;
    Source      *s;
    char        *tname = (char *)NULL;
    jmp_buf     ReturnPoint;

/* Check input file */

    if (hname == (char *)NULL)
        return -1;

/*
 * If processing for $, ` and ' is required, do it
 */

    if (sub)
    {
        CreateNewEnvironment ();

        if (SetErrorPoint (ReturnPoint) == 0)
        {
            if ((FP = FOpenFile (hname, sOpenReadMode)) == (FILE *)NULL)
            {
                PrintErrorMessage ("Here Document (%s) lost", hname);
                return -1;
            }

/* set up ScanNextToken input from here file */

            s = pushs (SFILE);
            s->u.file = FP;
            source = s;

            if (ScanNextToken (ONEWORD) != PARSE_WORD)
                PrintErrorMessage ("Here Document error");

            cp = ExpandAString (yylval.cp, 0);

    /* write expanded input to another temp file */

            tname = StringCopy (GenerateTemporaryFileName ());

            if ((FP = FOpenFile (tname, sOpenAppendMode)) == (FILE *)NULL)
            {
                ShellErrorMessage (Outofmemory1);
                TerminateCurrentEnvironment (TERMINATE_COMMAND);
            }

            fputs (cp, FP);
            CloseFile (FP);

            QuitCurrentEnvironment ();

            return S_open (TRUE, tname, O_RDONLY);
        }

/* Error - terminate */

        else
        {
            QuitCurrentEnvironment ();
            CloseFile (FP);

            if (tname != (char *)NULL)
                unlink (tname);

            return -1;
        }
    }

/* Otherwise, just open the document */

    return S_open (FALSE, hname, O_RDONLY);
}