Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1458 lines
44 KiB

/*** 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:
* 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
*
* Notes:
*
* Notes:
* Functions currently in this module ...
*
* buildArgumentVector - local
* doCommands - public (build.c)
* execLine - public (rpn.c)
* execCommand - local (undone, currently common to do & iterate)
* expandCommandLine - local
* fDoRedirection - local
* fEmulateCommand - local
* getComSpec - local
* iterateCommand - local
* redirect - local
* removeQuotes - local
* touch - local
*
*****************************************************************************/
/* INCLUDEs */
#include "nmake.h"
#include "nmmsg.h"
#include "proto.h"
#include "globals.h"
#include "grammar.h"
/* Constant DEFINEs */
#define numInternals (sizeof(internals) / sizeof(char *))
#define SLASH '\\'
#define PUBLIC
#define QUOTE '\"'
/* Extern PROTOTYPEs */
#ifndef NO_OPTION_Z
extern STRINGLIST * NEAR canonCmdLine(char *);
#endif
extern void NEAR copyMacroTable(MACRODEF *old[], MACRODEF *new[]);
#if defined(DOS) && !defined(FLAT)
extern int NEAR doSuperSpawn(char *, char **);
#endif
extern void NEAR freeEnviron(char **);
extern void NEAR freeMacroTable(MACRODEF *table[]);
extern BOOL NEAR processInline(char *, char **, STRINGLIST **);
extern char * NEAR SearchRunPath(char *, char *);
extern void CDECL NEAR makeIdeMessage (unsigned, unsigned,...);
#if defined(FLAT)
extern UCHAR fRunningUnderTNT;
#endif
/* Local PROTOTYPEs */
LOCAL void NEAR buildArgumentVector(unsigned*, char**, char *);
LOCAL char * NEAR expandCommandLine(void);
#if defined(DOS)
LOCAL BOOL NEAR fDoRedirection(char*, int*, int*);
LOCAL BOOL NEAR redirect(char*, unsigned);
#endif
LOCAL BOOL NEAR fEmulateCommand(int argc, char **argv, int *pStatus);
LOCAL char * NEAR getComSpec(void);
LOCAL BOOL NEAR iterateCommand(char*, STRINGLIST*, UCHAR, UCHAR, char *, unsigned*);
LOCAL void NEAR removeQuotes(int, char **);
LOCAL void NEAR touch(char*, BOOL);
/* Extern VARIABLEs */
//buffer for path of .cmd/.bat
extern char NEAR bufPath[];
extern char NEAR fileStr[MAXNAME];
extern char * NEAR initSavPtr;
extern char * NEAR makeStr;
#ifdef DEBUG_MEMORY
extern FILE *memory;
#endif
extern char * NEAR progName;
extern unsigned NEAR saveBytes;
extern char * NEAR shellName;
/* Local VARIABLEs */
#ifndef NO_OPTION_Z
LOCAL char batchIfCmd[] = "@if errorlevel %3d @goto NMAKEEXIT";
#endif
//cmd.exe and command.com internal commands
LOCAL char *internals[] = {
"BREAK", "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"
};
LOCAL char szCmdLineBuf[MAXCMDLINELENGTH];
/* FUNCTIONs in Alphabetical order */
/*** 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
* 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.
*
*****************************************************************************/
LOCAL void NEAR
buildArgumentVector(argc, argv, cmdline)
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 = _ftcschr(p = cmdline, '\0');
// Work around:
end = p = cmdline;
while (*end)
end++;
for (*argc = 0; *argc < MAXARG && p < end; ++*argc) {
p += _ftcsspn(p, " \t"); /* skip whitespace*/
if (p >= end)
break;
*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) {
if (*p == '\\')
++p; //skip escaped character
else if (*p == '\"')
break;
}
if (p >= end)
continue;
++p;
p = _ftcspbrk(p, " \t");
}
else {
/* For the first word on the command line, accept the switch
* character and whitespace as terminators. Otherwise, just
* whitespace.
*/
p = _ftcspbrk(p, " \t\"/");
for (;p && p < end;p = _ftcspbrk(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 = _ftcspbrk(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 == '/') {
*p = '\0';
argv[-1] = makeString(argv[-1]);
*p-- = '/';
}
}
}
if (!p)
p = end;
/* Now, p points to end of command line argument */
*p++ = '\0';
}
*argv = NULL;
}
PUBLIC int NEAR
doCommands(
char *name,
STRINGLIST *s,
STRINGLIST *t,
UCHAR buildFlags,
char *pFirstDep
) {
char *u,
*v;
UCHAR cFlags;
unsigned status = 0;
char c;
char *Cmd;
char *pLine;
BOOL fExpanded;
char *pCmd;
#ifndef NO_OPTION_Z
STRINGLIST *z, *zList; //For -z option
#endif
#ifdef DEBUG_ALL
if (fDebug)
{
printf("* doCommands: %s,\n", name);
DumpList(s);
DumpList(t);
}
#endif
#ifdef DEBUG_ALL
printf ("DEBUG: doCommands 1\n");
#endif
++numCommands;
if (ON(gFlags, F1_QUESTION_STATUS))
return(0);
makeIdeMessage (3, MSG_IDE_BUILD, name);
if (ON(gFlags, F1_TOUCH_TARGETS)) {
touch(name, (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);
cFlags = 0;
errorLevel = 0L;
u = Cmd;
for (v = u; *v; ++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 (!_ftcsncmp(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); 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 = strtol(u, &u, 10);
if (errno == ERANGE) {
*u = '\0';
makeError(line, CONST_TOO_BIG, pNumber);
}
while(_istspace(*u))
u++;
}
else errorLevel = 255;
--u;
break;
case '@': if (
#ifndef NO_OPTION_Z
OFF(gFlags, F1_REVERSE_BATCH_FILE) ||
#endif
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 (_ftcschr(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 > (unsigned)errorLevel)
break;
continue;
}
v = u;
#ifdef DEBUG_ALL
printf ("DEBUG: doCommands 2.23\n");
#endif
if (!fExpanded && _ftcschr(u, '$'))
u = expandMacros(u, &t);
#ifdef DEBUG_ALL
printf ("DEBUG: doCommands 2.24\n");
#endif
expandExtmake(CmdLine, u, pFirstDep);
#ifndef NO_OPTION_Z
if (ON(gFlags, F1_REVERSE_BATCH_FILE))
zList = canonCmdLine(CmdLine);
else {
zList = makeNewStrListElement();
zList->text = CmdLine;
}
#ifdef DEBUG_ALL
printf ("DEBUG: doCommands 2.3\n");
#endif
for (z = zList; z; z = z->next) {
pLine = z->text;
#else
pLine = CmdLine;
#endif
status = execLine(pLine,
(BOOL)(ON(buildFlags, F2_NO_EXECUTE)
|| (OFF(buildFlags,F2_NO_ECHO)
&& OFF(cFlags,C_SILENT))),
(BOOL)((OFF(buildFlags, F2_NO_EXECUTE)
#ifndef NO_OPTION_Z
&& OFF(gFlags, F1_REVERSE_BATCH_FILE)
#endif
)
|| ON(cFlags, C_EXECUTE)),
(BOOL)ON(cFlags, C_IGNORE), &pCmd);
if (OFF(buildFlags, F2_IGNORE_EXIT_CODES)) {
#ifndef NO_OPTION_Z
if (ON(gFlags, F1_REVERSE_BATCH_FILE)) {
STRINGLIST *revCmd;
revCmd = makeNewStrListElement();
revCmd->text = (char *)rallocate(_ftcslen(batchIfCmd) + 1);
sprintf(revCmd->text, batchIfCmd,
(errorLevel == 255 ? errorLevel: errorLevel + 1));
prependItem(&revList, revCmd);
}
else
#endif
if (status && status > (unsigned)errorLevel) {
if (!fOptionK)
makeError(0, BAD_RETURN_CODE, pCmd, status);
#ifndef NO_OPTION_Z
else
break;
#endif
}
}
#ifndef NO_OPTION_Z
}
#endif
if (v != u)
FREE(u);
#ifndef NO_OPTION_Z
if (ON(gFlags, F1_REVERSE_BATCH_FILE))
freeList(zList);
else
FREE_STRINGLIST(zList);
#endif
FREE(Cmd);
if (OFF(buildFlags, F2_IGNORE_EXIT_CODES) && fOptionK && status &&
status > (unsigned)errorLevel)
break;
}
#ifdef DEBUG_ALL
printf ("DEBUG: doCommands 3\n");
#endif
if (OFF(buildFlags, F2_IGNORE_EXIT_CODES) && fOptionK)
return(status);
else
return(0);
}
/*** 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
*
* Assumes:
* Whatever it assumes
*
* Modifies Globals:
* global -- how/what
*
* Uses Globals:
* global used and why
*
* 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 NEAR
execLine(
char *line,
BOOL echoCmd,
BOOL doCmd,
BOOL ignoreReturn,
char **ppCmd
) {
char *argv[3+MAXNAME/2];
BOOL fUseShell;
int oldIn = -1, //old stdin file handle
oldOut = -1, //old stdout file handle
status;
unsigned argc;
static char bufName[MAXNAME] = { 0 }; //Buffer for program name
BOOL fInternalCmd = FALSE;
static char szDrive[_MAX_DRIVE], szDir[_MAX_DIR], szFileName[_MAX_FNAME];
progName = NULL;
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 = strtol(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 = 255;
break;
}
//handle null command ...
if (!line[0])
return(0);
//copy command line into buffer
if (_ftcslen(line) < MAXCMDLINELENGTH)
_ftcscpy(szCmdLineBuf, line);
else
makeError(0, COMMAND_TOO_LONG, line);
#ifndef NO_OPTION_Z
//If -z and '$(MAKE)' then echo it
if (echoCmd && ON(gFlags, F1_REVERSE_BATCH_FILE)
&& !_ftcsnicmp(szCmdLineBuf, makeStr, _ftcslen(makeStr))) {
STRINGLIST *revCmd;
revCmd = makeNewStrListElement();
revCmd->text = (char *)rallocate(1 + _ftcslen(szCmdLineBuf) + 3 + 1);
sprintf(revCmd->text, "\t%s /Z%s", makeStr, szCmdLineBuf + _ftcslen(makeStr));
prependItem(&revList, revCmd);
return(0);
}
#endif
//If -n then echo command if not '$(MAKE)'
if (echoCmd
// 15-Apr-93 JonM ... we are no longer executing recursive makes if -n, so
// we want to echo them.
// && (_strnicmp(szCmdLineBuf, makeStr, strlen(makeStr)) ||
// OFF(flags, F2_NO_EXECUTE))
)
{
printf("\t%s\n", szCmdLineBuf);
fflush(stdout);
}
#if defined(DOS)
//for DOS use shell only if we have to because COMMAND.COM does not
//return child return codes; redirect, except for -n
fUseShell =
#if defined(FLAT)
!fRunningUnderTNT || // use shell only if TNT, not NT
#endif
(BOOL) (OFF(flags, F2_NO_EXECUTE) &&
fDoRedirection(szCmdLineBuf, &oldIn, &oldOut))
#ifndef NO_OPTION_Z
|| ON(gFlags, F1_REVERSE_BATCH_FILE)
#endif
;
#else
//for OS/2 let the shell do the work
fUseShell = TRUE;
#endif
/* 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(szCmdLineBuf);
/* Build arg vector. This is a waste on OS/2 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, 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 && !_ftcsicmp(argv[0], makeStr))
*argv = _pgmptr;
/* Copy program name into buffer. Can't just use argv[0] since this is
* from heap and will be freed before it may be used in an error message.
*/
if (argc)
progName = _ftcsncpy(bufName, argv[0], sizeof(bufName) - 1);
else
return(0); // for case when macro command is null
if (!doCmd) { /* don't execute command if doCmd false*/
#ifndef NO_OPTION_Z
if (ON(gFlags, F1_REVERSE_BATCH_FILE)) {
STRINGLIST *revCmd;
char *echoStr;
revCmd = makeNewStrListElement();
revCmd->text = (char *)rallocate(1 + _ftcslen(szCmdLineBuf) + 1);
echoStr = echoCmd ? "\t" : "@";
_ftcscat(_ftcscpy(revCmd->text, echoStr), szCmdLineBuf);
prependItem(&revList, revCmd);
}
#endif
//For -n, emulate if possible.
if (fEmulateCommand(argc, argv, &status)) {
if (status && ppCmd)
*ppCmd = makeString(*argv);
return(status); /* return status */
}
else
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))
fUseShell = FALSE;
#if defined(DOS)
else if (!fUseShell) {
int lo = 0, mid, result, hi = numInternals; //for binary search
errno = 0;
/* Do binary search of *argv in internal commands. */
for (mid = (hi+lo) / 2; hi - lo > 1; mid = (hi+lo) / 2) {
if (!(result = _ftcsicmp(*argv, internals[mid]))) {
fUseShell = TRUE;
break;
}
else if (result < 0) hi = mid;
else lo = mid;
}
fInternalCmd = TRUE;
if (!fUseShell) {
char *p;
/* Ignore any given extention. This is what DOS does. */
_splitpath( progName, szDrive, szDir, szFileName, NULL );
_makepath( progName, szDrive, szDir, szFileName, NULL );
// p = _ftcsrchr(progName, '.');
// if (p && p[1] != '\\' && p[1] != '/')
// *p = 0;
/* Search for the program in the search path. If found,
* p points to extention else NULL.
*/
p = SearchRunPath(progName, bufPath);
if (!p) {
/* If not found, set up an error since COMMAND will
* return 0. This risks future incompatibility if new
* DOS built-in commands are added.
*/
errno = ENOENT;
status = -1;
} else if (p[1] == 'b' || _ftcsicmp(p, ".cmd") == 0)
//If .bat extention, use COMMAND.COM.
fUseShell = TRUE;
else {
//Spawn command directly. Capitalize argv[0] since
//COMMAND.COM does.
for (p = *argv; *p; p++)
*p = (char)_totupper(*p);
#if defined(DOS)
#ifdef CHECK_CMD_LIMIT
if (_ftcslen(line) >= DOSCMDLINELIMIT)
makeError(0, COMMAND_TOO_LONG, line);
#endif
#endif
#ifdef USE_SUPER
status = doSuperSpawn(bufPath, argv);
#else
{
char * t = argv[0];
argv[0] = bufPath;
status = SPAWNVP(P_WAIT, bufPath, argv);
argv[0] = t;
}
#endif
}
}
}
#endif
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) {
_ftcscpy(szCmdLineBuf, line);
#if defined(DOS)
if (_ftcslen(line) >= DOSCMDLINELIMIT)
makeError(0, COMMAND_TOO_LONG, line);
for (p = szCmdLineBuf; *p && *p != ' ' && *p != '\t'; p++)
*p = (char)_totupper(*p);
#endif
#ifdef DEBUG_MEMORY
if (fDebug) {
mem_status();
fprintf(memory, "Spawning '%s'\n", szCmdLineBuf);
}
#endif
if (fInternalCmd)
status = SYSTEM(szCmdLineBuf);
else {
int i;
for (i=argc; i >= 0 ; i--) {
argv[i+2] = argv[i];
}
argv[0] = shellName;
argv[1] = "/c";
#ifdef USE_SUPER
status = doSuperSpawn(argv[0], argv);
#else
status = SPAWNVP(P_WAIT, argv[0], argv);
#endif
}
#ifdef DEBUG_MEMORY
if (fDebug)
mem_status();
#endif
}
//BUGBUG: NT version 262 has a problem with the way the run-time execs
// a process. When the run-time posts a WaitForSingleObject to
// make sure the process has finished, the kernal occasionally sets
// the exit status of the process to STATUS_THREAD_IS_TERMINATING
// (0xC000004B). We test here to make sure that case doesn't cause
// problems later on...
if (status == 0xc000004b)
{
fprintf(stderr, "spawn returned 0xc000004b ... Benign\n");
status = 0;
}
/* 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(*argv);
FREE(pCmdLineCopy);
return(status);
}
/*** 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()
*
************************************************************************/
LOCAL char * NEAR
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;
_ftcscpy(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 (!_ftcschr(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++ = (char)_totupper(*p);
for (;*p;) {
if (*p == '%') {
pExpandName = &ExpandName[0];
while (*++p != '%' && *p)
*pExpandName++ = (char)_totupper(*p);
*pExpandName = '\0';
if (!*p++) { //unmatched %;so don't expand
*pBuf='\0'; //from the environment; like set
_ftcscat(Buf, ExpandName);
pBuf += _ftcslen(ExpandName);
}
else { //matched %;so expand from the environment
EnvBuf[0] = '\0';
if ((pEnv = getenv(ExpandName)) != (char *)NULL) {
_ftcscat(EnvBuf, pEnv);
*pBuf='\0';
_ftcscat(Buf,EnvBuf);
pBuf += _ftcslen(EnvBuf);
}
}
}
else
*pBuf++ = *p++;
}
*pBuf = '\0';
_ftcscpy(szCmdLineBuf, Buf);
*posName = '\0';
posName = szCmdLineBuf + _ftcslen(Buf); //Offset into buf
return(posName);
}
#if defined(DOS)
/*
* fDoRedirection -- handle redirection if possible, else return TRUE
*
*/
LOCAL BOOL NEAR
fDoRedirection(p, oldIn, oldOut)
char *p;
int *oldIn;
int *oldOut;
{
BOOL in = FALSE,
out = FALSE;
BOOL fReturn = FALSE;
char *q;
unsigned which;
//save original string
char *t = p;
char *save = NULL;
while (q = _ftcspbrk(p, "\"<>|")) {
switch (*q) {
case '\"':
if (!(q = _ftcschr(q+1, '\"'))) {
fReturn = TRUE;
break;
}
p = ++q;
break;
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) {
_ftcscpy(p, save);
FREE(save);
}
if (in && *oldIn != -1) {
if (_dup2(*oldIn, _fileno(stdout)) == -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);
}
#endif // DOS
/*** 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.
*/
LOCAL BOOL NEAR
fEmulateCommand(
int argc,
char **argv,
int *pStatus
) {
char *pArg0 = *argv;
char *pArg1 = argv[1];
#if defined(SELF_RECURSE)
char *parentPtr;
MACRODEF **oldTable;
int i;
/* use local because global gets overwritten by second memmove */
BOOL fInhMacs = fInheritMacros;
#endif
/*
* If $(MAKE), save memory on recursive make's by saving the current
* state of the world and recursively calling doMake(). This saves
* the amount of memory taken up by NMAKE itself.
*/
#if !defined(SELF_RECURSE)
if (0) {}
#else
if (pArg0 == _pgmptr) { // if this is a recursive invocation
char **oldEnv;
#if defined(HEAP) && defined(TEST_RECURSION)
printf("\n**** BEFORE RECURSION ****\n");
heapdump(__FILE__, __LINE__);
#endif
parentPtr = (char *)rallocate(saveBytes);
memmove(parentPtr, &startOfSave, saveBytes);
if (fInhMacs) {
oldTable = (MACRODEF **)rallocate(MAXMACRO * sizeof(MACRODEF *));
copyMacroTable(macroTable, oldTable);
}
memmove(&startOfSave, initSavPtr, saveBytes);
/* UNDONE: Need to inherit /K and /O */
if (fInhMacs) {
for (i = 0; i < MAXMACRO; i++)
macroTable[i] = oldTable[i];
}
#ifndef NO_OPTION_Z
/* reinitialize makeflags variable */
if (ON(gFlags, F1_REVERSE_BATCH_FILE)) {
#ifdef PWB_HELP
char *p;
p = _ftcschr(getenv("MAKEFLAGS"), ' ');
*p = 'Z';
#else
return(TRUE);
#endif
}
#endif
removeQuotes(argc, argv);
// save old environ
oldEnv = environ;
// get new environ
environ = copyEnviron(environ);
*pStatus = doMake(argc, argv, parentPtr);
// free new environ; not needed anymore
freeEnviron(environ);
// restore old environ
environ = oldEnv;
if (fInhMacs) {
freeMacroTable(oldTable);
FREE(oldTable);
}
// make the heap less clustered by returning cleared area to the OS
_heapmin();
#if defined(HEAP) && defined(TEST_RECURSION)
printf("\n**** AFTER RECURSION ****\n");
heapdump(__FILE__, __LINE__);
#endif
return(TRUE);
}
#endif //of self-recursive section
else
/* If "<drive letter>:" then change drives. Ignore everything after
* the drive letter, just like the shell does.
*/
if (_istalpha(*pArg0) && pArg0[1] == ':' && !pArg0[2]) {
setdrive(_totupper(*pArg0) - 'A' + 1);
*pStatus = 0;
return(TRUE);
}
/* 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.
*/
else if (!_ftcsicmp(pArg0, "set")) {
if (argc == 1)
return(FALSE); // pass it to the shell
else {
char *pNameVal; // the "name=value" string
pNameVal = expandCommandLine();
/* if there is a syntax error let the shell handle it */
if (!*pNameVal)
return(FALSE);
if ((*pStatus = PutEnv(makeString(pNameVal))) == -1)
makeError(currentLine, OUT_OF_ENV_SPACE);
}
}
/* 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.
*/
else {
if (!_ftcsnicmp(pArg0, "cd", 2))
pArg0 += 2;
else if (!_ftcsnicmp(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.
*/
// Remove quotes, if any from the argument
removeQuotes(argc, argv);
//if there are more than two arguments then let the shell handle it
if (argc > 2)
return(FALSE);
else 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);
*pStatus = _chdir(pArg1);
}
else if (*pArg0 == '.' || PATH_SEPARATOR(*pArg0))
*pStatus = _chdir(pArg0);
else
/* Unrecognized syntax--we can't emulate. */
return(FALSE);
}
/* If error, simulate a return code of 1. */
if (*pStatus != 0)
*pStatus = 1;
return(TRUE);
}
/* ----------------------------------------------------------------------------
* 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.
*/
LOCAL char * NEAR
getComSpec()
{
void *findBuf = _alloca(resultbuf_size);
NMHANDLE searchHandle;
char *p;
char *shell;
if ((shell = getenv("COMSPEC")) != NULL) {
return(shell);
}
if ((p = getenv("PATH")) == NULL)
p = "";
#ifdef DOS
shell = searchPath(p, "COMMAND.COM", findBuf, &searchHandle);
#else
shell = searchPath(p, "CMD.EXE", findBuf, &searchHandle);
#endif
if (shell == NULL)
makeError(0, NO_COMMAND_COM);
return(shell);
}
LOCAL BOOL NEAR
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;
#ifndef NO_OPTION_Z
STRINGLIST *z, *zList; //For -z option
#endif
char *pCmd;
for (v = u; *v ; ++v) {
parens = FALSE;
if (*v == '$') {
if (*(v+1) == '(') {
++v;
parens = TRUE;
}
if (*(v+1) == '?') {
if (parens
&& !(_ftcschr("DFBR", *(v+2)) && *(v+3) == ')')
&& *(v+2) != ')')
continue;
p = dollarQuestion;
c = '?';
break;
}
if (*++v == '*' && *(v+1) == '*') {
if (parens
&& !(_ftcschr("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);
#ifndef NO_OPTION_Z
if (ON(gFlags, F1_REVERSE_BATCH_FILE))
zList = canonCmdLine(CmdLine);
else {
zList = makeNewStrListElement();
zList->text = CmdLine;
}
for (z = zList; z; z = z->next) {
pLine = z->text;
#else
pLine = CmdLine;
#endif
*status = execLine(pLine,
(BOOL)(ON(buildFlags, F2_NO_EXECUTE)
|| (OFF(buildFlags,F2_NO_ECHO)
&& OFF(cFlags,C_SILENT))),
(BOOL)((OFF(buildFlags, F2_NO_EXECUTE)
#ifndef NO_OPTION_Z
&& OFF(gFlags, F1_REVERSE_BATCH_FILE)
#endif
)
|| ON(cFlags, C_EXECUTE)),
(BOOL)ON(cFlags, C_IGNORE), &pCmd);
if (OFF(buildFlags, F2_IGNORE_EXIT_CODES)) {
#ifndef NO_OPTION_Z
if (ON(gFlags, F1_REVERSE_BATCH_FILE)) {
STRINGLIST *revCmd;
revCmd = makeNewStrListElement();
revCmd->text = (char *)rallocate(_ftcslen(batchIfCmd) + 1);
sprintf(revCmd->text, batchIfCmd,
(errorLevel == 255 ? errorLevel: errorLevel + 1));
prependItem(&revList, revCmd);
}
else
#endif
if (*status && *status > (unsigned)errorLevel)
if (!fOptionK)
makeError(0, BAD_RETURN_CODE, pCmd, *status);
}
#ifndef NO_OPTION_Z
}
#endif
if (c == '*')
dollarStarStar = dollarStarStar->next = p;
else dollarQuestion = dollarQuestion->next = p;
FREE(u);
#ifndef NO_OPTION_Z
if (ON(gFlags, F1_REVERSE_BATCH_FILE))
freeList(zList);
else
FREE_STRINGLIST(zList);
#endif
if (OFF(buildFlags, F2_IGNORE_EXIT_CODES) && fOptionK && *status &&
*status > (unsigned)errorLevel)
break;
}
if (c == '*') dollarStarStar = q;
else dollarQuestion = q;
return(TRUE);
}
#if defined(DOS)
/* 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.
*
*/
LOCAL BOOL NEAR
redirect(name, which)
char *name;
unsigned which;
{
char *p,
c = '\0';
BOOL fStatus;
char *mode;
FILE *stream;
FILE *new;
while (WHITESPACE(*name)) ++name;
if (p = _ftcspbrk(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";
}
new = freopen(name, mode, stream);
// if (!new) { // REVIEW: consider notifying the user
// perror(name); // REVIEW: that we failed here?
// } // REVIEW: this could save grief later...
fStatus = (BOOL)(new ? TRUE : FALSE);
if (fStatus && which == APPEND)
_lseek(_fileno(new), 0L, SEEK_END);
while(*name)
*name++ = ' ';
if (p)
*p = c;
return(fStatus);
}
#endif // DOS
LOCAL void NEAR
removeQuotes(argc, argv)
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
*(string++) = *(t++);
}
*string = '\0';
}
}
LOCAL void NEAR
touch(s, minusN)
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);
}
}