|
|
/*** 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, ¯os);
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); } }
|