//  Exec.C - Contains routines that have do to with execing programs
//
// Copyright (c) 1988-1991, Microsoft Corporation. All Rights Reserved.
//
// Purpose:
//  Contains routines that spawn programs ...
//
// Revision History:
//  04-Feb-2000 BTF Ported to Win64
//  15-Nov-1993 JdR Major speed improvements
//  15-Oct-1993 HV  Use tchar.h instead of mbstring.h directly, change STR*() to _ftcs*()
//  10-May-1993 HV  Add include file mbstring.h
//                  Change the str* functions to STR*
//  06-Oct-1992 GBS Removed extern for _pgmptr
//  10-Aug-1992 GBS Change file parsing in execLine to use splitpath
//  19-Aug-1992 SS  Remove Quotes from cd argument.
//  08-Jun-1992 SS  add IDE feedback support
//  08-Jun-1992 SS  Port to DOSX32
//  16-May-1991 SB  Created from routines that existed elsewhere

#include "precomp.h"
#pragma hdrstop

#define SLASH '\\'
#define PUBLIC
#define QUOTE '\"'

extern BOOL processInline(char *, char **, STRINGLIST **, BOOL);

#ifdef _M_IX86
extern UCHAR fRunningUnderChicago;
#else
#define fRunningUnderChicago FALSE
#endif

char * getComSpec(void);
BOOL   iterateCommand(char*, STRINGLIST*, UCHAR, UCHAR, char *, unsigned*);
void   removeQuotes(int, char **);
void   touch(char*, BOOL);


//buffer for path of .cmd/.bat
extern char * makeStr;
extern char * shellName;


char szCmdLineBuf[MAXCMDLINELENGTH];
char *szNmakeProgName;

//  buildArgumentVector -- builds an argument vector from a command line
//
// Scope:
//  Local.
//
// Purpose:
//  It builds an argument vector for a command line. This argument vector can
//  be used by spawnvX routines. The algorithm is explained in the notes below.
//
// Input:
//  argc    -- The number of arguments created in the argument vector
//  argv    -- The actual argument vector created
//              (Ignored if NULL)
//  cmdline -- The command line whose vector is required
//
// Output:
//  Returns the number of arguments and the argument vector as parameters
//
// Errors/Warnings:
// Assumes:
//  That the behaviour of cmd.exe i.e. parses quotes but does not disturb them.
//  Assumes that the SpawnVX routines will handle quotes as well as escaped
//  chars.
//
// Modifies Globals:
// Uses Globals:
// Notes:
//  Scan the cmdline from left to the end building the argument vector along
//  the way. Whitespace delimits arguments except for the first argument for
//  which the switch char '/' is also allowed. Backslash can be used to escape
//  a char and so ignore the character following it. Parse the quotes along
//  the way. If an argument begins with a double-quote then all characters till
//  an unescaped double-quote are part of that argument. Likewise, if an
//  unescaped Doublequote occurs within an argument then the above follows. If
//  the end of the command line comes before the closing quote then the
//  argument goes as far as that.

void
buildArgumentVector(
    unsigned *argc,
    char **argv,
    char *cmdline
    )
{
    char *p;                        // current loc in cmdline
    char *end;                      // end of command line
    BOOL    fFirstTime = TRUE;      // true if 1st argument

    // 11-May-1993 HV _mbschr() bug: return NULL
    // end = _tcschr(p = cmdline, '\0');
    // Work around:
    end = p = cmdline;
    while (*end)
        end++;

    for (*argc = 0; p < end; ++*argc) {
        p += _tcsspn(p, " \t");    // skip whitespace
        if (p >= end)
            break;
        if (argv)
            *argv++ = p;
        if (*p == '\"') {

            // If the word begins with double-quote, find the next
            // occurrence of double-quote which is not preceded by backslash
            // (same escape as C runtime), or end of string, whichever is
            // first.  From there, find the next whitespace character.

            for (++p; p < end; p = _tcsinc(p)) {
                if (*p == '\\')
                    ++p;            // skip escaped character
                else if (*p == '\"')
                    break;
            }
            if (p >= end)
                continue;
            ++p;
            p = _tcspbrk(p, " \t");
        } else {

            // For the first word on the command line, accept the switch
            // character and whitespace as terminators.  Otherwise, just
            // whitespace.

            p = _tcspbrk(p, " \t\"/");
            for (;p && p < end;p = _tcspbrk(p+1, " \t\"/")) {
                if (*p == '/' && !fFirstTime)
                    continue;       // after 1st word '/' is !terminator
                else break;
            }
            if (p && *p == '\"') {
                for (p++;p < end;p++) {     // inside quote so skip to next one
                    if (*p == '\"')
                        break;
                }
                p = _tcspbrk(p, " \t");    // after quote go to first whitespace
            }
            if (fFirstTime) {
                fFirstTime = FALSE;

                // If switch char terminates the word, replace it with 0,
                // re-allocate the word on the heap, restore the switch and set
                // p just before the switch.  It would be easier to shift
                // everything right but then we have to worry about overflow.

                if (p && *p == '/' && argv) {
                    *p = '\0';
                    argv[-1] = makeString(argv[-1]);
                    *p-- = '/';
                }
            }
        }
        if (!p)
            p = end;
        // Now, p points to end of command line argument
        if (argv)
            *p++ = '\0';
    }
    if (argv)
        *argv = NULL;
}

PUBLIC int
doCommands(
    char *name,
    STRINGLIST *s,
    STRINGLIST *t,
    UCHAR buildFlags,
    char *pFirstDep
    )
{
    STRINGLIST *temp;
    int rc;
    temp = makeNewStrListElement();
    temp->text = makeString(name);
    rc = doCommandsEx (temp, s, t, buildFlags, pFirstDep);
    free_stringlist(temp);
    return rc;
}

PUBLIC int
doCommandsEx(
    STRINGLIST *nameList,
    STRINGLIST *s,
    STRINGLIST *t,
    UCHAR buildFlags,
    char *pFirstDep
    )
{
    char *u, *v;
    UCHAR cFlags;
    unsigned status = 0;
    int retryCount = 0;
    char c;
    char *Cmd;
    char *pLine;
    BOOL fExpanded;
    char *pCmd;
    size_t cbLine;

#ifdef DEBUG_ALL
    if (fDebug) {
        printf("* doCommands:");
        DumpList(nameList);
        DumpList(s);
        DumpList(t);
    }
#endif

#ifdef DEBUG_ALL
    printf("DEBUG: doCommands 1\n");
#endif
    ++numCommands;
    if (ON(gFlags, F1_QUESTION_STATUS))
        return(0);

    if (ON(gFlags, F1_TOUCH_TARGETS)) {
        STRINGLIST *pName;
        for (pName = nameList; pName; pName = pName->next) {
            touch(pName->text, (USHORT) ON(buildFlags, F2_NO_EXECUTE));
        }
        return(0);
    }

#ifdef DEBUG_ALL
    printf("DEBUG: doCommands 2\n");
#endif

    for (; s; s = s->next) {
        fExpanded = processInline(s->text, &Cmd, &t,
            ON(buildFlags, F2_DUMP_INLINE));
        cFlags = 0;
        errorLevel = 0;
        u = Cmd;
        for (v = u; *v; v = _tcsinc(v)) {
            if (*v == ESCH) ++v;
            else if (*v == '$') {
                if (*++v == '$')
                    continue;
// commented out 15-Apr-93 by JonM.  This code forces recursive nmake to be
// executed even if -n, but it's hosed (the -n is not passed to the recursive
// nmake), and the whole thing sounds like a bad idea anyway, so I'm going to
// turn it off.
//              if (!_tcsncmp(v, "(MAKE)", 6)) {
//                  SET(cFlags, C_EXECUTE);
//                  break;
//              }
            }
        }
#ifdef DEBUG_ALL
    printf("DEBUG: doCommands 2.1\n");
#endif
        for (c = *u; c == '!'  ||
                     c == '-'  ||
                     c == '@'  ||
                     c == ESCH ||
                     WHITESPACE(c); u = _tcsinc(u), c = *u) {
            switch (c) {
                case ESCH:
                    if (c = *++u, WHITESPACE(c))
                        c = ' ';    // keep going
                    else
                        c = ESCH;
                    break;

                case '!':
                    SET(cFlags, C_ITERATE);
                    break;

                case '-':
                    SET(cFlags, C_IGNORE);
                    ++u;
                    if (_istdigit(*u)) {
                        char *pNumber = u;

                        errorLevel = _tcstoul(u, &u, 10);
                        if (errno == ERANGE) {
                            *u = '\0';
                            makeError(line, CONST_TOO_BIG, pNumber);
                        }
                        while(_istspace(*u))
                            u++;
                    } else
                        errorLevel = UINT_MAX;
                    --u;
                    break;
                case '@':
                    if (
                        OFF(flags, F2_NO_EXECUTE)) {
                            SET(cFlags, C_SILENT);
                        }
                    break;
            }
            if (c == ESCH)
                break;              // stop parsing for cmd-line options
        }
#ifdef DEBUG_ALL
        printf("DEBUG: doCommands 2.2\n");
#endif
        if (ON(cFlags, C_ITERATE) &&
            iterateCommand(u, t, buildFlags, cFlags, pFirstDep, &status)
           ) {
            // The macros used by the command have to be freed & so we do so

            v = u;

#ifdef DEBUG_ALL
            printf("DEBUG: doCommands 2.21\n");
#endif
            if (_tcschr(u, '$'))
                u = expandMacros(u, &t);

#ifdef DEBUG_ALL
            printf("DEBUG: doCommands 2.22\n");
#endif
            if (v != u)
                FREE(u);
            if (OFF(buildFlags, F2_IGNORE_EXIT_CODES) &&
                fOptionK &&
                status &&
                status > errorLevel)
            {
                break;
            }
            continue;
        }
        v = u;

#ifdef DEBUG_ALL
        printf("DEBUG: doCommands 2.23\n");
#endif
        if (!fExpanded && _tcschr(u, '$'))
            u = expandMacros(u, &t);

#ifdef DEBUG_ALL
        printf("DEBUG: doCommands 2.24\n");
#endif

        cbLine = _tcslen(u) + 1;
        pLine = (char *) rallocate (__max(cbLine, MAXCMDLINELENGTH));
        _tcscpy(pLine, u);

        // by this time $< has already been expanded.
        // in order to allow processing of long commands that are due to
        // batch-mode rules, use a buffer that may be larger than MAXCMDLINELENGTH
        // Later we'll attempt to execute the long command directly, instead of
        // passing it to the shell.
        // Note: the macros expanded by ZFormat are not normally found in the
        // command block of a batch-mode rule, so it should be safe to use
        // max(cbLine, MAXCMDLINELENGTH) as a limit for ZFormat
        if (ZFormat (pLine, __max(cbLine, MAXCMDLINELENGTH), u, pFirstDep))
            makeError(0, COMMAND_TOO_LONG, u);

retry:
            status = execLine(pLine,
                              (BOOL)(ON(buildFlags, F2_NO_EXECUTE)
                                  || (OFF(buildFlags,F2_NO_ECHO)
                                  && OFF(cFlags,C_SILENT))),
                              (BOOL)((OFF(buildFlags, F2_NO_EXECUTE)
                                     )
                                     || ON(cFlags, C_EXECUTE)),
                              (BOOL)ON(cFlags, C_IGNORE), &pCmd);
            if (OFF(buildFlags, F2_IGNORE_EXIT_CODES)) {
                if (status == STATUS_PENDING) {
                    // Hack for ntvdm problem returning correct error code.
                    if (retryCount < 10) {
                        retryCount++;
                        goto retry;
                    }
                }
                if (status && status > errorLevel) {
                    if (!fOptionK)
                        makeError(0, BAD_RETURN_CODE, pCmd, status);
                }
            }
        if (v != u)
            FREE(u);
        FREE(Cmd);
        FREE(pLine);
        if (OFF(buildFlags, F2_IGNORE_EXIT_CODES) &&
            fOptionK &&
            status &&
            status > errorLevel)
        {
            break;
        }
    }

#ifdef DEBUG_ALL
    printf("DEBUG: doCommands 3\n");
#endif

    if (OFF(buildFlags, F2_IGNORE_EXIT_CODES) && fOptionK &&
            (status > errorLevel))
        return(status);
    else
        return(0);
}


//  expandCommandLine -- expands %name% strings in the Command Line
//
// Purpose:
//  The function expands '%name%' type strings in the Command Line. Its main
//  job is to assist FEmulateCommand() in emulating set for OS/2.
//
// Modifies:    buf -- The Command Line available globally
//
// Output:
//  Returns -- the position of 'name=value' part in the Command Line.
//          -- Null when no '=' is found so that FEmulateCommand() can pass the
//              line to the shell to signal syntax error.
// Note:
//  The shell does not give a syntax error for unmatched '%' and assumes it
//  as just another character in this case. This behaviour is duplicated
//  by expandCommandLine()

char *
expandCommandLine(
    void
    )
{
    char Buf[MAXCMDLINELENGTH];         // Buffer for expanded string
    char *pBuf;
    char EnvBuf[MAXCMDLINELENGTH];      // getenv returned string copy
    char *posName,                      // position of 'name=string' in Buf or buf
         *p,                            // points into buf
         *pEnv;                         // points into Env
    char ExpandName[MAXNAME];           // %name% string
    char *pExpandName;

    pBuf = Buf;
    _tcscpy(pBuf, "set");
    p = szCmdLineBuf + 3;               // go beyond 'set'
    pBuf +=3;
    /* Skip whitespace */
    for (;;p++) {
        if (!(WHITESPACE(*p)))
            break;                      // argc>1 � this will happen
        else *pBuf++ = *p;
    }

    if (!_tcschr(p, '='))
        return("");                     // Syntax error so pass to the shell
    else
        posName = pBuf;                 // fixes position of Name in Buf

    // Now we look for environment variables and expand if required
    for (;*p != '=';p++)
        *pBuf++ = *p;

    for (;*p;) {
        if (*p == '%') {
            pExpandName = &ExpandName[0];
            while (*++p != '%' && *p)
                *pExpandName++ = *p;
            *pExpandName = '\0';
            if (!*p++) {                // unmatched %;so don't expand
                *pBuf='\0';             // from the environment; like set
                _tcscat(Buf, ExpandName);
                pBuf += _tcslen(ExpandName);
                break;                  // Done precessing quit #43290
            } else {                    // matched %;so expand from the environment
                EnvBuf[0] = '\0';
                if ((pEnv = getenv(ExpandName)) != (char *)NULL) {
                    *pBuf='\0';

                    // If the expanded command line is too long
                    // just say that we can't expand it!!! #43290
                    size_t len = _tcslen(pEnv) + _tcslen(Buf);
                    if (len > MAXCMDLINELENGTH)
                        return NULL;

                    _tcscat(EnvBuf, pEnv);
                    _tcscat(Buf,EnvBuf);
                    pBuf += _tcslen(EnvBuf);
                }
            }
        } else
            *pBuf++ = *p++;
    }
    *pBuf = '\0';
    _tcscpy(szCmdLineBuf, Buf);
    *posName = '\0';
    posName = szCmdLineBuf + _tcslen(Buf);          // Offset into buf
    return(posName);
}

// expandEnvVars -- expands %name% strings in szArg
//
// Returns -- szNew: the resulting expanded string
//          (szNew should be FREEd by the caller)
//
char *
expandEnvVars(
    char *szArg
    )
{
    char *pchLeft = NULL;
    char *pchRight = NULL;
    char *pchStart = szArg;

    char *szNew = makeString("");

    while (*pchStart) {
        pchLeft = _tcschr(pchStart, '%');
        if (pchLeft) {
            pchRight = _tcschr(pchLeft + 1, '%');
        }

        if (pchLeft && pchRight) {
            char *szEnv;
            *pchLeft = '\0';
            *pchRight = '\0';
            szNew = reallocString(szNew, pchStart);
            if (szEnv = getenv(pchLeft + 1)) {
                szNew = reallocString(szNew, szEnv);
            }
            else {
                // no matching env var was found
                // append the %..% string literary
                *pchLeft = '%';
                szNew = reallocString(szNew, pchLeft);
                szNew = reallocString(szNew, "%");
            }
            *pchLeft = '%';
            *pchRight = '%';
            pchStart = pchRight + 1;
            pchLeft = NULL;
            pchRight = NULL;
        }
        else {
            szNew = reallocString(szNew, pchStart);
            pchStart += _tcslen(pchStart);
        }
    }
    return szNew;
}


//  FEmulateCommand - look for certain commands and emulate them
//
// Emulate $(MAKE), cd, chdir, and <drive letter>:.
// Also emulates 'set'.
//
// RETURNS:    TRUE if command emulated, FALSE if not.
//
// Note:
//  In set emulation if a syntax error is discovered then it lets the
//  shell handle it. It does this by returning FALSE.

BOOL
FEmulateCommand(
    int argc,
    char **argv,
    int *pStatus
    )
{
    char *pArg0 = argv[0];
    char *pArg1 = argv[1];

    if (_istalpha(*pArg0) && pArg0[1] == ':' && !pArg0[2]) {
        // If "<drive letter>:" then change drives.  Ignore everything after
        // the drive letter, just like the shell does.

        _chdrive(_totupper(*pArg0) - 'A' + 1);
        *pStatus = 0;
        return(TRUE);
    }

    if (!_tcsicmp(pArg0, "set")) {
        char *pNameVal;         // the "name=value" string

        // If "set" then pass it to the shell and if "set string" then put it
        // into the environment. Let the shell handle the syntax errors.

        if (argc == 1) {
            return(FALSE);          // pass it to the shell
        }

        // expandCommandLine cannot handle lines > MAXCMDLINELENGTH
        // In that case szCmdLineBuf will be empty
        if (!szCmdLineBuf[0])
            return (FALSE);

        pNameVal = expandCommandLine();

        if (pNameVal == NULL)
        {
            // Expanded commad line too long
            return FALSE;
        }

        if (!*pNameVal) {
            // If there is a syntax error let the shell handle it

            return(FALSE);
        }

        if ((*pStatus = PutEnv(makeString(pNameVal))) == -1) {
            makeError(currentLine, OUT_OF_ENV_SPACE);
        }
    } else {
        // If "cd foo" or "chdir foo", do a chdir() else in protect mode this
        // would be a no-op.  Ignore everything after 1st arg, just like the
        // shell does.

        char *szArg;

        if (!_tcsnicmp(pArg0, "cd", 2)) {
            pArg0 += 2;
        } else if (!_tcsnicmp(pArg0, "chdir", 5)) {
            pArg0 += 5;
        } else {
            return(FALSE);
        }

        // At this point, a prefix of argv[0] matches cd or chdir and pArg0
        // points to the next char.  Check for a path separator in argv[0]
        // (e.g., cd..\foo) or else use the next arg if present.

        // if there are more than two arguments then let the shell handle it
        if (argc > 2) {
            return(FALSE);
        }

        // Remove quotes, if any from the argument
        removeQuotes(argc, argv);

        if (!*pArg0 && pArg1) {
            // Under certain circumstances the C RunTime does not help us
            // e.g. 'd:', in this case let the shell do it ...
            if (isalpha(*pArg1) && pArg1[1] == ':' && !pArg1[2]) {
                return(FALSE);
            }

            szArg = expandEnvVars(pArg1); // [VS98 2251]
            *pStatus = _chdir(szArg);
            FREE (szArg);
        } else if (*pArg0 == '.' || PATH_SEPARATOR(*pArg0)) {
            szArg = expandEnvVars(pArg0); // [VS98 2251]
            *pStatus = _chdir(szArg);
            FREE (szArg);
        } else {
            // Unrecognized syntax--we can't emulate.

            return(FALSE);
        }
    }

    // If error, simulate a return code of 1.

    if (*pStatus != 0) {
        *pStatus = 1;
    }

    return(TRUE);
}

#ifdef WIN95

int __cdecl
cmpSzPsz(
    const void *sz,
    const void *psz
    )
{
   const char *sz1 = (char *) sz;
   const char *sz2 = *(char **) psz;

   return(_tcsicmp(sz1, sz2));
}


BOOL
FInternalCommand(
    const char *szName
    )
{
    const char * const *pszInternal;

    static const char * const rgszInternal[] =
    {
        "BREAK",
        "CALL",
        "CD",
        "CHDIR",
        "CLS",
        "COPY",
        "CTTY",
        "DATE",
        "DEL",
        "DIR",
        "DIR.",
        "ECHO",
        "ECHO.",
        "ERASE",
        "EXIT",
        "FOR",
        "GOTO",
        "IF",
        "MD",
        "MKDIR",
        "PATH",
        "PAUSE",
        "PROMPT",
        "RD",
        "REM",
        "REN",
        "RENAME",
        "RMDIR",
        "SET",
        "SHIFT",
        "TIME",
        "TYPE",
        "VER",
        "VERIFY",
        "VOL"
    };


    pszInternal = (const char * const *) bsearch(szName,
                                                 rgszInternal,
                                                 sizeof(rgszInternal) / sizeof(rgszInternal[0]),
                                                 sizeof(rgszInternal[0]),
                                                 &cmpSzPsz);

    return(pszInternal != NULL);
}

#endif  // WIN95

// redirect -- handles redirection of input or output.
//
// arguments:   dir - READ => input,
//                    WRITE => output,
//                    APPEND => append to end of the file.
//
//              p - pointer to buffer that has the filename as
//                  well as the rest of the command string.
//
// return value     FALSE => error (freopen fails)
//                  TRUE => normal return.
//
// the freopen() call sets up the redirection. the rest of the
// command string is then copied forward.

BOOL
redirect(
    char *name,
    unsigned which
    )
{
    char *p;
    char c = '\0';
    BOOL fStatus;
    char *mode;
    FILE *stream;
    FILE *newFile;

    while (WHITESPACE(*name)) {
        name++;
    }

    if (p = _tcspbrk(name, " \t<>\r")) {
        c = *p;

        *p = '\0';
    }

    if (which == READ) {
        mode = "r";
        stream = stdin;
    } else {
        stream = stdout;

        if (which == WRITE) {
            mode = "w";
        } else {
            mode = "a";
        }
    }

    newFile = freopen(name, mode, stream);

    fStatus = (newFile != NULL);

    if (fStatus && (which == APPEND)) {
        _lseek(_fileno(newFile), 0L, SEEK_END);
    }

    while (*name) {
        *name++ = ' ';
    }

    if (p) {
        *p = c;
    }

    return(fStatus);
}


BOOL
FDoRedirection(
    char *p,
    int *oldIn,
    int *oldOut
    )
{
    BOOL in = FALSE;
    BOOL out = FALSE;
    BOOL fReturn = FALSE;
    char *q;
    unsigned which;
    char *save = NULL;


    while (q = _tcspbrk(p, "<>|")) {
        switch (*q) {
            case '<':
                if (in) {
                    fReturn = TRUE;
                    break;
                }

                if (!save) {
                    save = makeString(p);
                }

                *q++ = ' ';
                p = q;
                in = TRUE;
                *oldIn = _dup(_fileno(stdin));

                if ((*oldIn == -1) || !redirect(q, READ)) {
                    fReturn = TRUE;
                    break;
                }
                break;

            case '>':
                if (out) {
                    fReturn = TRUE;
                    break;
                }

                if (!save) {
                    save = makeString(p);
                }

                *q++ = ' ';
                p = q;
                out = TRUE;

                if (*q == '>') {
                    *q++ = ' ';
                    which = APPEND;
                } else {
                    which = WRITE;
                }

                *oldOut = _dup(_fileno(stdout));

                if ((*oldOut == -1) || !redirect(q, which)) {
                    fReturn = TRUE;
                    break;
                }
                break;

            case '|':
                fReturn = TRUE;
                break;

            default :
                makeError(0, BUILD_INTERNAL);
        }

        if (fReturn) {
            break;
        }
    }

    if (fReturn) {
        if (save != NULL) {
            _tcscpy(p, save);
            FREE(save);
        }

        if (in && (*oldIn != -1)) {
            if (_dup2(*oldIn, _fileno(stdin)) == -1) {
                makeError(0, BUILD_INTERNAL);
            }

            _close(*oldIn);

            *oldIn = -1;
        }

        if (out && (*oldOut != -1)) {
            if (_dup2(*oldOut, _fileno(stdout)) == -1) {
                makeError(0, BUILD_INTERNAL);
            }

            _close(*oldOut);

            *oldOut = -1;
        }

    }

    return(fReturn);
}


BOOL
FSearchForExecutableExt(
    const char *szFilename,
    const char *szExt,
    BOOL fHasPath,
    char *szPath
    )
{
    char szFullName[_MAX_PATH];

    _tcscpy(szFullName, szFilename);
    _tcscat(szFullName, szExt);
    if (fHasPath) {
       if (_access(szFullName, 0) == 0) {
          _tcscpy(szPath, szFullName);

          return(TRUE);
       }

       return(FALSE);
    }

    _searchenv(szFullName, "PATH", szPath);

    return(szPath[0] != '\0');
}


BOOL
FSearchForExecutable(char *szFullName, char *szPath, BOOL *fBat)
{
    char szDrive[_MAX_DRIVE];
    char szDir[_MAX_DIR];
    char szFileName[_MAX_FNAME];
    char szNoExt[_MAX_PATH];
    BOOL fHasPath;
    char *szEndQuote;
    BOOL fHasQuotes = FALSE;

    // Ignore any given extension.  This is what COMMAND.COM does,

    char *szToPass = szFullName;

    if (*szFullName == QUOTE) {
        // get rid of any number of quotes at the beginning and at the end of the
        // string. This allows handling names enclosed in multiple quotes that are
        // accepted by the shell (DS 14300)
        szEndQuote = _tcsdec(szFullName, szFullName + _tcslen(szFullName));

        if (QUOTE == *szEndQuote) {
            fHasQuotes = TRUE;

            while (QUOTE == *szToPass)
                szToPass ++;

            while (szEndQuote > szToPass) {
                char *szPrev = _tcsdec (szToPass, szEndQuote);
                if (QUOTE != *szPrev)
                    break;
                szEndQuote = szPrev;
            }

            *szEndQuote = '\0';
        }
    }

    _splitpath(szToPass, szDrive, szDir, szFileName, NULL);
    _makepath(szNoExt, szDrive, szDir, szFileName, NULL);
    fHasPath = (szDrive[0] != '\0') || (szDir[0] != '\0');

    *fBat = FALSE;

    // Search for .COM file

    if (FSearchForExecutableExt(szNoExt, ".com", fHasPath, szPath)) {
        goto success;
    }

    // Search for .EXE file

    if (FSearchForExecutableExt(szNoExt, ".exe", fHasPath, szPath)) {
        goto success;
    }

    // Search for .BAT file

    if (FSearchForExecutableExt(szNoExt, ".bat", fHasPath, szPath)) {
        *fBat = TRUE;

        goto success;
    }

    return(FALSE);

success:
    if (fHasQuotes) {
        size_t size = _tcslen(szPath);
        memmove(szPath+1, szPath, size);
        *szPath = '"';
        *(szPath + size + 1) = '"';
        *(szPath + size + 2) = '\0';
        *szEndQuote = '"';
    }
    return TRUE;

}


//  execLine -- execute a command line
//
// Scope:   Global (build.c, rpn.c)
//
// Purpose:
//  Parses the command line for redirection characters and redirects stdin and
//  stdout if "<", ">", or ">>" are seen.  If any of the following occur,
//  restore the original stdin and stdout, pass the command to the shell, and
//  invoke the shell:
//     - the command line contains "|" (pipe)
//     - a syntax error occurs in parsing the command line
//     - an error occurs in redirection
//  Otherwise, attempt to invoke the command directly, then restore the
//  original stdin and stdout.  If this invocation failed because of
//  file-not-found then pass the command to the shell and invoke the shell.
//
// Input:   line         -- The command line to be executed
//          echoCmd      -- determines if the command line is to be echoed
//          doCmd        -- determines if the command is to be actually executed
//          ignoreReturn -- determines if NMAKE is to ignore the return code on
//                          execution
//          ppCmd        -- if non-null then on error returns command executed
//
// Output:  Returns ... return code from child process
//                  ... -1 if error occurs
//
// Notes:
//  1/ Quoted strings can have redir chars "<>" which will be skipped over.
//  2/ Unmatched quotes cause error; redir chars are replaced by space char.
//  3/ Dup stdin file handle then redirect it. If we have to use the shell,
//     restore the original command line.
//  4/ Emulate certain commands such as "cd" to help prevent some makefiles
//     from breaking when ported from DOS to OS/2.
//
// Algorithm for spawning commands:
//  If we can't handle the syntax, let the shell do everything.  Otherwise,
//  first check to see if the command (without extension) is a DOS built-in &
//  if it is, call the shell to execute it (this is how cmd.exe behaves)
//  If it's not a built-in, we check to see if it has a .cmd or a .bat
//  extension (depending on whether we're in DOS or OS/2). If it does, we
//  call system() to execute it.
//  If it has some other extension, we ignore the extension and go looking for
//  a .cmd or .bat file.  If we find it, we execute it with system().
//  Otherwise, we try to spawn it (without extension). If the spawn fails,
//  we issue an unknown program error.

int
execLine(
    char *line,
    BOOL echoCmd,
    BOOL doCmd,
    BOOL ignoreReturn,
    char **ppCmd
    )
{
    char **argv;
    BOOL fUseShell;
    BOOL fLongCommand;
    int status;
    unsigned argc;

    if (!shellName) {
        shellName = getComSpec();
    }

    switch (*line) {
        case '@':
            // Turn off echo if it was on.  This handles the case where the "@"
            // was in a macro.
            //
            line++;
            if (doCmd)
                echoCmd = 0;
            break;

        case '-':
            ignoreReturn = TRUE;
            ++line;
            if (_istdigit(*line)) {
                char * pNumber = line;
                errorLevel = _tcstoul(line, &line, 10);
                if (errno == ERANGE) {
                    *line = '\0';
                    makeError(0, CONST_TOO_BIG, pNumber);       // Todo: replace 0 with line number
                }
                while(_istspace(*line))
                      line++;
            } else
                errorLevel = UINT_MAX;
            break;
    }

    // handle null command ...
    if (!line[0])
        return(0);

#if 0
    // 10/10/96: disabled to allow execution of long
    // commands that are produced by batch-mode rules

    // copy command line into buffer
    if (_tcslen(line) < MAXCMDLINELENGTH)
        _tcscpy(szCmdLineBuf, line);
    else
        makeError(0, COMMAND_TOO_LONG, line);
#endif

    fLongCommand = _tcslen(line) >= MAXCMDLINELENGTH;
    if (!fLongCommand)
        _tcscpy(szCmdLineBuf, line);
    else
        *szCmdLineBuf = '\0';

    // Allocate a copy of the command line on the heap because in a
    // recursive call to doMake(), argv pointers will be allocated from
    // the static buffer which will then be trashed.  For buildArg...().

    pCmdLineCopy = makeString(line);

    // If -n then echo command if not '$(MAKE)'
    if (echoCmd) {
        printf("\t%s\n", pCmdLineCopy);
        fflush(stdout);
    }

    // Build arg vector.  This is a waste on Windows NT since we're probably
    // going to use the shell, except we have to check for cd, $(MAKE),
    // etc. so we take advantage of the parsing code.

    buildArgumentVector(&argc, NULL, pCmdLineCopy);

    if (argc == 0) {
        return(0);                     // for case when macro command is null
    }

    // allocate argv. Leave space for extra arguments
    // (like "cmd", "/k", quotes) that may be added later
    argv = (char **) rallocate((argc + 5) * sizeof (char *));
    buildArgumentVector(&argc, argv, pCmdLineCopy);


    // 11-May-1993 HV The _mbsicmp() does not like NULL pointer
    //                so I have to check before calling it.
    if (argv[0] && makeStr && !_tcsicmp(argv[0], makeStr)) {
        if(!szNmakeProgName) {
            szNmakeProgName = _pgmptr;
            if( _tcspbrk( szNmakeProgName," " )) {      // If the program name has an embedded space in it
                // Let's put quotes around it
                szNmakeProgName = (char *)rallocate(_tcslen(szNmakeProgName)+3);
                *szNmakeProgName = QUOTE;               // First quote
                *(szNmakeProgName+1) = '\0';
                _tcscat( szNmakeProgName, _pgmptr );    // copy the full program name (self)
                _tcscat( szNmakeProgName, "\"");        // Final quote and \0
            }
        }
        argv[0]=szNmakeProgName;
    }

    if (!doCmd) {                   // don't execute command if doCmd false
        // For -n, emulate if possible.

        if (FEmulateCommand(argc, argv, &status)) {
            if (status && ppCmd) {
                *ppCmd = makeString(*argv);
            }

            return(status);         // return status
        }

        return(0);
    }

    // Try emulating the command if appropriate.  If not, and we should not
    // use the shell, try spawning command directly.

    // Check status when emulating

    if (FEmulateCommand(argc, argv, &status)) {
        // Command has been emulated.  Don't execute it again.

        fUseShell = FALSE;

    } else if (!fRunningUnderChicago && !fLongCommand) {
        // Use the shell for Windows NT unless the command is too long

        fUseShell = TRUE;

#ifdef WIN95
    } else if (fRunningUnderChicago && FInternalCommand(argv[0])) {
        // Under Windows 95 or MS-DOS, use the shell for internal commands

        fUseShell = TRUE;
#endif  // WIN95

    } else {
        int oldIn = -1;                // Old stdin file handle
        int oldOut = -1;               // Old stdout file handle

        // Under Windows 95 or MS-DOS, COMMAND.COM doesn't return child return
        // codes.  Try spawning the child application directly.

        // This code is also now used if the line is too long to be handled by
        // the NT command interpreter.

        fUseShell = FDoRedirection(line, &oldIn, &oldOut);

        if (!fUseShell) {
            char szPath[_MAX_PATH];
            char szQuotedPath[_MAX_PATH];
            BOOL fBat;

            if (oldIn != -1 || oldOut != -1) { // If there was a redirection
                // Need to re-build the argument vector without the
                // redirection characters
                FREE(pCmdLineCopy);
                pCmdLineCopy = makeString(line);
                buildArgumentVector(&argc, argv, pCmdLineCopy);
            }

            if (!FSearchForExecutable(argv[0], szPath, &fBat)) {
                /* If not found, set up an error since COMMAND will
                 * return 0.  This risks future incompatibility if new
                 * COMMAND.COM internal commands are added.
                 */
                if (fRunningUnderChicago) {
                    errno = ENOENT;
                    status = -1;
                } else {
                    fUseShell = TRUE;
                }
            } else if (fBat) {
                // If .bat extension, use COMMAND.COM.

                // UNDONE: CreateProcess is supposed to handle this.  Try it.

                fUseShell = TRUE;
            } else {
                // Spawn command directly.
                // DevStudio#8911, cannot use quotes in szPath
                if (*szPath == QUOTE && *(szPath + _tcslen(szPath) - 1) == QUOTE) {
                    // unquote the path.
                    size_t  cb = _tcslen(szPath);
                    memmove(szPath, szPath + 1, cb);
                    *(szPath + cb - 2) = '\0';
                }
#if 0
                {
                    int i;
                    printf("Spawning \"%s\" directly\n", szPath);
                    for (i = 0; i < argc; i++) {
                        printf ( "Arg[%d] = \"%s\"\n", i, argv[i] );
                    }
                }
#endif
                // DS 14300: Use full path for argv[0]
                // otherwise a shell command may be invoked
                // instead of an intended executable with the
                // same name. Enclosing quotes are needed if
                // string has embedded spaces

                argv[0] = szPath;
                if (_tcschr (argv[0], ' ')) {
                    *szQuotedPath = QUOTE;
                    _tcscpy (szQuotedPath+1, szPath);
                    _tcscat (szQuotedPath, "\"");
                    argv[0] = szQuotedPath;
                }
                status = (int)_spawnvp(P_WAIT, szPath, argv);   // REVIEW:WIN64 cast
            }
        }

        if (oldIn != -1) {
            if (_dup2(oldIn, _fileno(stdin)) == -1) {
                makeError(0, BUILD_INTERNAL);
            }

            _close(oldIn);
        }

        if (oldOut != -1) {
            if (_dup2(oldOut, _fileno(stdout)) == -1) {
                makeError(0, BUILD_INTERNAL);
            }

            _close(oldOut);
        }
    }

    if (fUseShell) {
        int i;
        BOOL fExtraQuote = FALSE;

        // copy command line into buffer
        if (_tcslen(line) < MAXCMDLINELENGTH)
            _tcscpy(szCmdLineBuf, line);
        else
            makeError(0, COMMAND_TOO_LONG, line);

        // Workaround for cmd bug (DevStudio #11253):
        // IF argv[0] (before we rearrange with cmd.exe /c) is quoted AND
        // any of the other argv[1...n] args have quotes   AND
        // running on NT
        // THEN we add an extra quote before argv[0] and one after argv[n].

        if ((*argv[0] == QUOTE) &&
            (*(argv[0] + _tcslen(argv[0]) - 1) == QUOTE) &&
            !fRunningUnderChicago) {
            for (i = argc - 1; i >= 1; i--) {
                if( _tcspbrk( argv[i],"\"" )) {
                    fExtraQuote = TRUE;
                    break;
                }
            }
        }

        if (fExtraQuote) {
            argv[argc++] = "\"";
            argv[argc] = NULL;
        }

        for (i = argc; i >= 0; i--) {
            argv[i+2] = argv[i];
        }

        argv[0] = shellName;
        argv[1] = fExtraQuote ? "/c \"" : "/c";

#if 0
        printf("Shelling \"%s\"\n", szCmdLineBuf);
        for (i = 0; i < argc + 2; i++) {
            printf ( "Arg[%d] = \"%s\"\n", i, argv[i] );
        }
#endif

        status = (int)_spawnvp(P_WAIT, argv[0], (const char * const *) argv);   // REVIEW:WIN64 cast
    }

    // Check for errors spawning command (distinct from errors *returned*
    // from a successfully spawned command).

    if (status == -1) {
        if (ignoreReturn) {
            status = 0;
        } else {
            switch (errno) {
                case 0:
                    // We (ie: nmake) didn't fail, but the spawned program did.
                    break;

                case ENOENT:
                    makeError(0, CANT_FIND_PROGRAM, argv[0]);
                    break;

                case ENOMEM:
                    makeError(0, EXEC_NO_MEM, fUseShell ? argv[2] : argv[0]);
                    break;

                default:
                    // Done to flag possibly erroneous decision made here [SB]
                    makeError(0, SPAWN_FAILED_ERROR, _strerror(NULL));
            }
        }
    }

    if (status && ppCmd) {
        *ppCmd = makeString(fUseShell ? argv[2] : argv[0]);
    }

    FREE(argv);
    FREE(pCmdLineCopy);
    return(status);
}


// getComSpec()
//
// actions:        Attempts to find system shell.
//
// First look for COMSPEC.  If not found, look for COMMAND.COM or CMD.EXE
// in the current directory then the path.  If not found, fatal error.
// It would make sense to give an error if COMSPEC is not defined but
// test suites are easier if no user-defined environment variables are
// required.

char *
getComSpec()
{
    char *szShell;
    char szPath[_MAX_PATH];

    if ((szShell = getenv("COMSPEC")) != NULL) {
        return(szShell);
    }

    if (fRunningUnderChicago) {
        szShell = "COMMAND.COM";
    } else {
        szShell = "CMD.EXE";
    }

    _searchenv(szShell, "PATH", szPath);

    if (szPath[0] == '\0') {
        makeError(0, NO_COMMAND_COM);
    }

    return(makeString(szPath));
}


BOOL
iterateCommand(
    char *u,
    STRINGLIST *t,
    UCHAR buildFlags,
    UCHAR cFlags,
    char *pFirstDep,
    unsigned *status
    )
{
    BOOL parens;
    char c = '\0';
    char *v;
    STRINGLIST *p = NULL,
               *q;
    char *pLine;
    char *pCmd;

    for (v = u; *v ; ++v) {
        parens = FALSE;
        if (*v == '$') {
            if (*(v+1) == '(') {
                ++v;
                parens = TRUE;
            }
            if (*(v+1) == '?') {
                if (parens
                    && !(_tcschr("DFBR", *(v+2)) && *(v+3) == ')')
                    && *(v+2) != ')')
                    continue;
                p = dollarQuestion;
                c = '?';
                break;
            }
            if (*++v == '*' && *(v+1) == '*') {
                if (parens
                    && !(_tcschr("DFBR", *(v+2)) && *(v+3) == ')')
                    && *(v+2) != ')')
                    continue;
                p = dollarStarStar;
                c = '*';
                break;
            }
        }
    }

    if (!*v) {
        return(FALSE);
    }

    v = u;
    q = p;
    while (p) {
        macros = t;
        if (c == '*') {
            p = dollarStarStar->next;
            dollarStarStar->next = NULL;
        } else {
            p = dollarQuestion->next;
            dollarQuestion->next = NULL;
        }
        u = expandMacros(v, &macros);

        expandExtmake(CmdLine, u, pFirstDep);
            pLine = CmdLine;
            *status = execLine(pLine,
                              (BOOL)(ON(buildFlags, F2_NO_EXECUTE)
                                  || (OFF(buildFlags,F2_NO_ECHO)
                                     && OFF(cFlags,C_SILENT))),
                              (BOOL)((OFF(buildFlags, F2_NO_EXECUTE)
                                     )
                                     || ON(cFlags, C_EXECUTE)),
                              (BOOL)ON(cFlags, C_IGNORE), &pCmd);
            if (OFF(buildFlags, F2_IGNORE_EXIT_CODES)) {
                if (*status && *status > errorLevel)
                    if (!fOptionK)
                        makeError(0, BAD_RETURN_CODE, pCmd, *status);
            }

        if (c == '*')
            dollarStarStar = dollarStarStar->next = p;
        else
            dollarQuestion = dollarQuestion->next = p;
        FREE(u);
        if (OFF(buildFlags, F2_IGNORE_EXIT_CODES) &&
            fOptionK &&
            *status &&
            *status > errorLevel)
        {
            break;
        }
    }
    if (c == '*')
        dollarStarStar = q;
    else
        dollarQuestion = q;
    return(TRUE);
}


void
removeQuotes(
    int argc,
    char **argv
    )
{
    char *t,
         *string;

    for (; argc--; argv++) {
        string = *argv;
        for (t = string; *t;) {
            if (*t == SLASH || *t == ESCH) {
                if (t[1] == QUOTE)
                   *(string)++ = *(t++);
                *(string++) = *(t++);
                continue;
            }
            if (*t == QUOTE)
                ++t;
            else {
                if (_istlead(* (unsigned char *)t))
                    *(string++) = *(t++);
                *(string++) = *(t++);
            }
        }
        *string = '\0';
    }
}

void
touch(
    char *s,
    BOOL minusN
    )
{
    int fd;
    char c;
    FILE * file;

    makeMessage(TOUCHING_TARGET, s);
    if (!minusN &&
            ((file = FILEOPEN(s, "r+b")) != NULL)) {
        fd = _fileno(file);
        if (_read(fd, &c, 1) > 0) {
            _lseek(fd, 0L, SEEK_SET);
            _write(fd, &c, 1);
        }
        _close(fd);
    }
}