windows-nt/Source/XPSP1/NT/sdktools/nmake/exec.cpp
2020-09-26 16:20:57 +08:00

1594 lines
44 KiB
C++
Raw Permalink Blame History

// 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 <20> 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);
}
}