Leaked source code of windows server 2003
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.
 
 
 
 
 
 

4373 lines
137 KiB

/*++
Copyright (c) 1988-1999 Microsoft Corporation
Module Name:
cbatch.c
Abstract:
Batch file processing
--*/
#include "cmd.h"
struct batdata *CurrentBatchFile = NULL;
int EchoFlag = E_ON; /* E_ON = commands are to be echoed */
int EchoSave; /* M016 - Save echo status here */
extern int Necho;
BOOLEAN GotoFlag = FALSE; /* TRUE = eGoto() found a label */
TCHAR *Fvars = NULL;
TCHAR **Fsubs = NULL;
TCHAR *save_Fvars = NULL; /* @@ */
TCHAR **save_Fsubs = NULL; /* @@ */
int FvarsSaved = FALSE; /* @@ */
extern UINT CurrentCP;
extern ULONG DCount; /* M031 */
extern unsigned DosErr; /* M033 */
extern unsigned flgwd; /* M040 */
/* M011 - Removed RemStr, BatSpecStr, NewBatName and OldBatName from
* external declarations below.
*/
extern TCHAR CurDrvDir[];
extern TCHAR Fmt02[], Fmt11[], Fmt12[], Fmt13[], Fmt15[], Fmt17[], Fmt18[]; /* M024 */
extern TCHAR Fmt20[]; /* M017/M024 */
extern TCHAR Fmt00[]; /* @@4 */
extern TCHAR TmpBuf[]; /* M030 - Used for GOTO search */
extern CHAR AnsiBuf[];
extern TCHAR GotoStr[];
extern TCHAR GotoEofStr[];
extern TCHAR ForStr[];
extern TCHAR ForLoopStr[];
extern TCHAR ForDirTooStr[];
extern TCHAR ForParseStr[];
extern TCHAR ForRecurseStr[];
extern int LastRetCode;
extern TCHAR chCompletionCtrl;
extern TCHAR chPathCompletionCtrl;
extern unsigned global_dfvalue; /* @@4 */
extern TCHAR LexBuffer[]; /* @@4 */
extern TCHAR SwitChar; /* M020 - Reference global switch byte */
extern BOOL CtrlCSeen;
void CheckCtrlC();
extern jmp_buf MainEnv;
#define BIG_BAT_NEST 200
#define MAX_STACK_USE_PERCENT 90
BOOLEAN flChkStack;
int CntBatNest;
PVOID FixedPtrOnStack;
typedef struct {
PVOID Base;
PVOID GuardPage;
PVOID Bottom;
PVOID ApprxSP;
} STACK_USE;
STACK_USE GlStackUsage;
#define DEFAULT_DELIMS TEXT( " \t" )
// to handle OS/2 vs DOS errorlevel setting rules in a script files.
int glBatType = NO_TYPE;
/*** ChkStack - check the stack usage
*
* Args:
* pFixed - fixed pointer on stack
* pStackUse - struct stack info to return to caller
*
* Returns:
* FAILURE - if stack info is not good
* SUCCESS - otherwise
*
* Notes:
* See the comments below about Stack Pointer
*
*
*/
int ChkStack (PVOID pFixed, STACK_USE *pStackUse )
{
MEMORY_BASIC_INFORMATION Mbi;
PVOID BasePtr;
PCHAR WalkPtr;
int cnt;
PVOID ThreadStackBase,
ThreadStackLimit;
CHAR VarOnStack; // keep this automatic var. here !
// 950119 the best (right) way to find the Current Stack Pointer is
// to write assembly code for all platforms.
// I implemented the most portable code. It should work OK with current NT
// memory models. If NT memory models change then a lot of code will have to
// be re-written anyway. Several NT projects rely on same assumption.
// I also have consistency test for all pointers.
pStackUse->ApprxSP = (VOID *) &VarOnStack; // address of automatic variable
// should be close to current SP
// suggested by MarkL 950119
ThreadStackBase = (PVOID) (NtCurrentTeb()->NtTib.StackBase );
ThreadStackLimit = (PVOID) (NtCurrentTeb()->NtTib.StackLimit );
if ( (pStackUse->ApprxSP >= ThreadStackBase) ||
(pStackUse->ApprxSP <= ThreadStackLimit ) )
return(FAILURE);
if ( (pFixed >= ThreadStackBase) ||
(pFixed <= ThreadStackLimit ) )
return(FAILURE);
// 1. Pass fixed on-the-stack pointer to find out the base address.
if ( (VirtualQuery (pFixed, &Mbi, sizeof(Mbi) ) ) != sizeof (Mbi) )
return(FAILURE);
BasePtr = Mbi.AllocationBase;
// 2. walk all the Virtual Memory Regions with same Allocation Base Address.
cnt = 0;
for (WalkPtr = (CHAR *)BasePtr; Mbi.AllocationBase == BasePtr; WalkPtr += Mbi.RegionSize) {
if ( (VirtualQuery ( (PVOID) WalkPtr, &Mbi, sizeof(Mbi) ) ) != sizeof (Mbi) )
return(FAILURE);
if (cnt == 0) {
if (Mbi.BaseAddress != Mbi.AllocationBase)
return(FAILURE);
}
if (Mbi.Protect & PAGE_GUARD)
pStackUse->GuardPage = Mbi.BaseAddress;
if (Mbi.AllocationBase == BasePtr)
pStackUse->Bottom = (PVOID) ( ( (CHAR *) Mbi.BaseAddress) + Mbi.RegionSize);
cnt++;
if (cnt >= 1000) // normally there are 3 regions : committed, guard, reserved.
return(FAILURE);
}
pStackUse->Base = BasePtr;
if ( pStackUse->Bottom != ThreadStackBase)
return(FAILURE);
if ( ( pStackUse->Base != GlStackUsage.Base) ||
( pStackUse->Bottom != GlStackUsage.Bottom ) ||
( pStackUse->Bottom <= pStackUse->Base ) )
return(FAILURE);
return(SUCCESS);
}
/*** BatAbort - terminate the batch processing unconditionally.
*
* Notes:
* Similar to CtrlCAbort()
*
*
*/
void BatAbort ()
{
struct batdata *bdat;
//
// End local environments ( Otherwise we can end up with garbage
// in the main environment if any batch file used the setlocal
// command ).
//
if (CurrentBatchFile) {
bdat = CurrentBatchFile;
while ( bdat ) {
EndAllLocals( bdat );
bdat = bdat->backptr;
}
}
SigCleanUp();
CntBatNest = 0;
longjmp(MainEnv, 1);
}
//
// Used to set and reset ctlcseen flag
//
VOID SetCtrlC();
VOID ResetCtrlC( );
/*** BatProc - does the set up before and the cleanup after batch processing
*
* Purpose:
* Set up for the execution of a batch job. If this job is being
* chained, (will come here only if part of compound statement),
* use the existing batch data structure thereby ending execution
* of the existing batch job (though still keeping its stack and data
* usage). If this is the first job or this job is being called,
* allocate a new batch data structure. In either case, use SetBat
* to fill the structure and prepare the job, then call BatLoop to
* at least begin the execution. When this returns at completion,
* check the env and dircpy fields of the data structure to see if
* the current directory and environment need to be reset. Finally,
* turn on the echoflag if no more batch jobs are on the stack.
*
* There are 3 ways to execute a batch job. They are:
* 1. Exactly as DOS 3.x. This is the default method and
* occurs whenever a batch file is simply executed at the
* command line or chained by another batch file. In the
* former case, it is the first job and will go through
* BatProc, else it will be detected in BatLoop and will
* will simply replace its parent.
* 2. Nested via the CALL statement. This is new functionality
* and provides the means of executing the child batch
* file and returning to the parent.
* 3. Invocation of an external batch processor via ExtCom()
* which then executes the batch file. This is accomplished
* by the first line of the batch file being of the form:
*
* ExtProc <batch processor name> [add'l args]
*
* int BatProc(struct cmdnode *n, TCHAR *fname, int typflag)
*
* Args:
* n - parse tree node containing the batch job command
* fname - the name of the batch file (MUST BE MAX_PATH LONG!)
* typflg - 0 = Normal batch file execution
* 1 = Result of CALL statement
*
* Returns:
* FAILURE if the batch processor cannot execute the batch job.
* Otherwise, the retcode of the last command in which was executed.
*
*/
int BatProc(n, fname, typflg)
struct cmdnode *n;
TCHAR *fname;
int typflg; /* M011 - "how called" flag */
{
struct batdata *bdat; /* Ptr to new batch data struct */
int batretcode; /* Retcode - last batch command */
int istoplevel;
SIZE_T StackUsedPerCent;
STACK_USE StackUsage;
#ifdef USE_STACKAVAIL // unfortunately not available
if ( stackavail() < MINSTACKNEED ) { /* If not enough stack @@4 */
/* space, stop processing */
PutStdErr(MSG_TRAPC,ONEARG,Fmt00); /* @@4 */
return(FAILURE);
}
#endif
DEBUG((BPGRP,BPLVL,"BP: fname = %ws argptr = %ws", fname, n->argptr));
/* M016 - If this is the first batch file executed, the interactive echo
* status is saved for later restoration.
*/
if (!CurrentBatchFile) {
EchoSave = EchoFlag;
istoplevel = 1;
CntBatNest = 0;
} else
istoplevel = 0;
// to check stack only if we are looping too much,
// to avoid unnecessary overhead
if (flChkStack && ( CntBatNest > BIG_BAT_NEST ) ) {
if ( ChkStack (FixedPtrOnStack, &StackUsage) == FAILURE ) {
flChkStack = 0;
} else {
GlStackUsage.GuardPage = StackUsage.GuardPage;
GlStackUsage.ApprxSP = StackUsage.ApprxSP;
StackUsedPerCent = ( ( (UINT_PTR)StackUsage.Bottom - (UINT_PTR)StackUsage.ApprxSP) * 100 ) /
( (UINT_PTR)StackUsage.Bottom - (UINT_PTR)StackUsage.Base );
if ( StackUsedPerCent >= MAX_STACK_USE_PERCENT ) {
PutStdErr(MSG_ERROR_BATCH_RECURSION,
TWOARGS,
CntBatNest,
StackUsedPerCent );
// if ^C was reported by "^C thread" then handle it here, before calling BatAbort().
CheckCtrlC();
BatAbort();
}
}
}
if (typflg)
CntBatNest++;
/* M011 - Altered to conditionally build a new data structure based on the
* values of typflg and CurrentBatchFile. Provided the first structure has
* been built, chained files no longer cause a new structure, while
* CALLed files do. Also, backpointer and CurrentBatchFile are set here
* rather than in BatLoop() as before. Finally, note that the
* file position indicator bdat->filepos must be reset to zero now
* when a new file is exec'd. Otherwise a chained file using the old
* structure would start off where the last one ended.
*/
if (typflg || !CurrentBatchFile) {
DEBUG((BPGRP,BPLVL,"BP: Making new structure"));
bdat = (struct batdata *) mkstr(sizeof(struct batdata));
if ( ! bdat )
return( FAILURE );
bdat->backptr = CurrentBatchFile;
} else {
DEBUG((BPGRP,BPLVL,"BP: Using old structure"));
bdat = CurrentBatchFile;
}
CurrentBatchFile = bdat; /* Takes care of both cases */
/* M011 ends */
bdat->stackmin = DCount; /* M031 - Fix datacount */
mystrcpy(TmpBuf,fname); /* Put where expected */
if (SetBat(n, fname)) /* M031 - All work done */
return(FAILURE); /* ...in SetBat now */
#ifndef WIN95_CMD
// Following two CmdBatNotification calls are being made to
// let NTVDM know that the binary is coming from a .bat/.cmd
// file. Without this all those DOS .bat programs are broken which
// first run a TSR and then run a real DOS app. There are a lot
// of such cases, Ventura Publisher, Civilization and many more
// games which first run a TSR. If .bat/.cmd does'nt have any
// DOS binary these calls dont have any effect.
if (istoplevel) {
// to determine the type of the script file: CMD or BAT
// to decide how to handle the errorlevel
glBatType = BAT_TYPE; // default
if (fname && (mystrlen(fname) >= 5) ) {
PTCHAR tmp;
tmp = fname + mystrlen(fname) - 1;
if ( ( (*tmp == TEXT ('D')) || (*tmp == TEXT ('d')) ) &&
( (*(tmp-1) == TEXT ('M')) || (*(tmp-1) == TEXT ('m')) ) &&
( (*(tmp-2) == TEXT ('C')) || (*(tmp-2) == TEXT ('c')) ) &&
( *(tmp-3) == DOT ) ) {
glBatType = CMD_TYPE;
}
}
CmdBatNotification (CMD_BAT_OPERATION_STARTING);
}
#endif // WIN95_CMD
batretcode = BatLoop(bdat,n); /* M039 */
if (istoplevel) {
#ifndef WIN95_CMD
CmdBatNotification (CMD_BAT_OPERATION_TERMINATING);
#endif // WIN95_CMD
CntBatNest = 0;
glBatType = NO_TYPE;
}
DEBUG((BPGRP, BPLVL, "BP: Returned from BatLoop"));
DEBUG((BPGRP, BPLVL, "BP: bdat = %lx CurrentBatchFile = %lx",bdat,CurrentBatchFile));
/* M011 - Now that setlocal and endlocal control the saving and restoring
* of environments and current directories, it is necessary to
* check each batch data structure before popping it off the stack
* to see if its file issued a SETLOCAL command. EndAllLocals() tests
* the env and dircpy fields, doing nothing if no localization
* needs to be reset. No tests need be done before calling it.
*/
if (CurrentBatchFile == bdat) {
DEBUG((BPGRP, BPLVL, "BP: bdat=CurrentBatchFile, calling EndAllLocals"));
EndAllLocals(bdat);
CurrentBatchFile = bdat->backptr;
if (CntBatNest > 0)
CntBatNest--;
}
if (CurrentBatchFile == NULL) {
EchoFlag = EchoSave; /* M016 - Restore echo status */
CntBatNest = 0;
}
DEBUG((BPGRP, BPLVL, "BP: Exiting, CurrentBatchFile = %lx", CurrentBatchFile));
return(batretcode);
}
/*** BatLoop - controls the execution of batch files
*
* Purpose:
* Loop through the statements in a batch file. Do the substitution.
* If this is the first statement and it is a REM command, call eRem()
* directly to check for possible external batch processor invocation.
* Otherwise, call Dispatch() to execute it and continue.
*
* BatLoop(struct batdata *bdat, struct cmdnode *c) (M031)
*
* Args:
* bdat - Contains info needed to execute the current batch job
* c - The node for this batch file (M031)
*
* Returns:
* The retcode of the last command in the batch file.
*
* Notes:
* Execution should end if the target label of a Goto command is not
* found, a signal is received or an unrecoverable error occurs. It
* will be indicated by the current batch data structure being
* popped off the batch jobs stack and is detected by comparing
* CurrentBatchFile and bdat. If they aren't equal, something happened so
* return.
*
* GotoFlag is reset everytime through the loop to make sure that
* execution resumes after a goto statement is executed.
*
*/
BatLoop(bdat,c)
struct batdata *bdat;
struct cmdnode *c;
{
struct node *n; /* Ptr to next statement */
BOOL fSilentNext;
int firstline = TRUE; /* TRUE = first valid line */
CRTHANDLE fh; /* Batch job file handle */
int batretcode = SUCCESS; /* Last Retcode (M008 init) */
fSilentNext = FALSE;
for (; CurrentBatchFile == bdat; ) {
CheckCtrlC();
GotoFlag = FALSE;
//
// If extensions are enabled, this is the first line in the
// file and it begins with a COLON, then we got here via
// CALL :label, so turn this into a GOTO :label command
// as BatProc/SetBat have already done the work of pushing
// our state and parsing the arguments.
//
if (fEnableExtensions && firstline && *c->cmdline == COLON) {
struct cmdnode *c1;
c1 = (struct cmdnode *)mknode();
if (c1 == NULL) {
PutStdErr(MSG_NO_MEMORY, NOARGS );
return( FAILURE );
}
c1->type = CMDTYP;
c1->cmdline = mkstr((mystrlen(GotoStr)+1)*sizeof(TCHAR));
if (c1->cmdline == NULL) {
PutStdErr(MSG_NO_MEMORY, NOARGS );
return FAILURE;
}
mystrcpy(c1->cmdline, GotoStr);
c1->argptr = mkstr((mystrlen(c->cmdline)+1)*sizeof(TCHAR));
if (c1->argptr == NULL) {
PutStdErr(MSG_NO_MEMORY, NOARGS );
return FAILURE;
}
mystrcpy(c1->argptr, c->cmdline);
*(c1->argptr) = SPACE;
//
// Set a flag so eGoTo does not try to abort a FOR loop
// because of one of these new CALL forms.
//
c1->flag = CMDNODE_FLAG_GOTO;
//
// Then again, maybe not. I have to think about this some
// more.
//
c1->flag = 0;
n = (struct node *)c1;
//
// Since we generated this GOTO statement, dont let the user
// know
//
fSilentNext = TRUE;
} else {
//
// Open and position the batch file to where next statement
//
if ((fh = OpenPosBat(bdat)) == BADHANDLE)
return( FAILURE); /* Ret if error */
DEBUG((BPGRP, BPLVL, "BLOOP: fh = %d", (ULONG)fh));
n = Parser(READFILE, (INT_PTR)fh, bdat->stacksize); /* Parse */
bdat->filepos = _tell(fh); // next statement
Cclose(fh);
if ((n == NULL) || (n == (struct node *) EOS)) {
continue;
}
DEBUG((BPGRP, BPLVL, "BLOOP: node = %x", n));
DEBUG((BPGRP, BPLVL, "BLOOP: fpos = %lx", bdat->filepos));
/* If syntax error, it is impossible to continue so abort. Note that
* the Abort() function doesn't return.
*/
if ( ( n == (struct node *)PARSERROR) || /* If error...*/
/* @@4 */ ( global_dfvalue == MSG_SYNERR_GENL ) )
/* @@4 */
{
PSError();
if ((EchoFlag == E_ON) && !Necho) {
DEBUG((BPGRP, BPLVL, "BLOOP: Displaying Statement."));
PrintPrompt();
PutStdOut(MSG_LITERAL_TEXT,ONEARG,&LexBuffer[1]);
}
Abort(); /* ...quit */
}
if (n == (struct node *) EOF) /* If EOF... */
return(batretcode); /* ...return also */
}
DEBUG((BPGRP, BPLVL, "BLOOP: type = %d", n->type));
/* M008 - By the addition of the second conditional term (&& n), any
* leading NULL lines in the batch file will be skipped without
* penalty.
*/
if (firstline && n) /* Kill firstline... */
firstline = FALSE; /* ...when passed */
/* M008 - Don't prompt, display or dispatch if statement is label for Goto
* M009 - Altered second conditional below to test for REMTYP. Was a test
* for CMDTYP and a strcmpi with the RemStr string.
*/
if (n->type == CMDTYP &&
*(((struct cmdnode *) n)->cmdline) == COLON)
continue;
/* M019 - Added extra conditional to test for leading SILent node
*/
if (fSilentNext)
fSilentNext = FALSE;
else
if (EchoFlag == E_ON && n->type != SILTYP && !Necho) {
DEBUG((BPGRP, BPLVL, "BLOOP: Displaying Statement."));
PrintPrompt();
DisplayStatement(n, DSP_SIL); /* M019 */
cmd_printf(CrLf); /* M026 */
}
if ( n->type == SILTYP ) { /* @@ take care of */
n = n->lhs; /* @@ recursive batch files */
} /* endif */
/* M031 - Chained batch files no longer go through dispatch. They become
* simply an extention of the current one by adding their redirection
* and replacing the current batch data information with their own.
*/
if ( n == NULL ) {
batretcode = SUCCESS;
} else if (n->type == CMDTYP &&
FindCmd(CMDHIGH, ((struct cmdnode *)n)->cmdline, TmpBuf) == -1 &&
/* M035 */ !mystrchr(((struct cmdnode *)n)->cmdline, STAR) &&
/* M035 */ !mystrchr(((struct cmdnode *)n)->cmdline, QMARK) &&
SearchForExecutable((struct cmdnode *)n, TmpBuf) == SFE_ISBAT) {
DEBUG((BPGRP, BPLVL, "BLOOP: Chaining to %ws", bdat->filespec));
if ((n->rio && AddRedir(c,(struct cmdnode *)n)) ||
SetBat((struct cmdnode *)n, bdat->filespec)) {
return(FAILURE);
}
firstline = TRUE;
batretcode = SUCCESS;
} else {
DEBUG((BPGRP, BPLVL, "BLOOP: Calling Dispatch()..."));
DEBUG((BPGRP, BPLVL, "BLOOP: ...node type = %d",n->type));
batretcode = Dispatch(RIO_BATLOOP, n);
{
extern CPINFO CurrentCPInfo;
ResetConsoleMode();
//
// Get current CodePage Info. We need this to decide whether
// or not to use half-width characters.
//
GetCPInfo((CurrentCP=GetConsoleOutputCP()), &CurrentCPInfo);
//
// Maybe console output code page was changed by CHCP or MODE,
// so need to reset LanguageID to correspond to code page.
//
#if !defined( WIN95_CMD )
CmdSetThreadUILanguage(0);
#endif
}
}
}
DEBUG((BPGRP, BPLVL, "BLOOP: At end, returning %d", batretcode));
DEBUG((BPGRP, BPLVL, "BLOOP: At end, CurrentBatchFile = %lx", CurrentBatchFile));
DEBUG((BPGRP, BPLVL, "BLOOP: At end, bdat = %lx", bdat));
return(batretcode);
}
/*** SetBat - Replaces current batch data with new. (M031)
*
* Purpose:
* Causes a chained batch file's information to replace its parent's
* in the current batch data structure.
*
* SetBat(struct cmdnode *n, TCHAR *fp)
*
* Args:
* n - pointer to the node for the chained batch file target.
* fp - pointer to filename found for batch file.
* NOTE: In addition, the batch filename will be in TmpBuf at entry.
*
* Returns:
* FAILURE if memory could not be allocated
* SUCCESS otherwise
*
* Notes:
* - WARNING - No allocation of memory must occur above the call to
* FreeStack(). When this call occurs, all allocated heap space
* is freed back to the empty batch data structure and its filespec
* string. Any allocated memory would also be freed.
* - The string used for "->filespec" is that malloc'd by ECWork or
* eCall during the search for the batch file. In the case of
* calls from BatLoop, the existing "->filespec" string is used
* by copying the new batch file name into it. THIS STRING MUST
* NOT BE RESIZED!
*
*/
int
SetBat(struct cmdnode *n, PTCHAR fp)
{
int i; // Index counters
int j;
TCHAR *s; // Temp pointer
SAFER_CODE_PROPERTIES CodeProps = {sizeof(SAFER_CODE_PROPERTIES), SAFER_CRITERIA_IMAGEPATH, 0};
SAFER_LEVEL_HANDLE Level = NULL;
DEBUG((BPGRP,BPLVL,"SETBAT: Entered"));
CurrentBatchFile->hRestrictedToken = NULL; // Restricted token for batchfile
//
// We delay load these routines in order to allow CMD to work on downlevel versions
//
ReportDelayLoadErrors = FALSE;
try {
//
// Now get the restricted token to be used for this batch file. The batch file
// may appear in TmpBuf (for normal batch execution or calls) or, in the case of
// CALL :LABEL
// the name appears in CurrentBatchFile->backptr->filespec
//
CodeProps.ImagePath = (WCHAR *) TmpBuf;
if (fEnableExtensions && *n->cmdline == COLON) {
CodeProps.ImagePath = (WCHAR *)CurrentBatchFile->backptr->filespec;
}
//
// Identify the level at which the code should be run.
//
if (SaferIdentifyLevel(1, &CodeProps, &Level, NULL)) {
//
// Compute the token from the level.
//
// Temp until Safer gets fixed
// BOOL FailAlways = _tcsstr( TmpBuf, TEXT( "disallowed" )) != NULL;
if (
// !FailAlways &&
SaferComputeTokenFromLevel(Level, NULL, &CurrentBatchFile->hRestrictedToken, SAFER_TOKEN_NULL_IF_EQUAL, NULL)) {
//
// All is well. We have successfuly computed a restricted token for
// the batch file. Close the handle to authorization level.
//
SaferCloseLevel(Level);
//
// Impersonate if a restricted token was returned by authorization.
// The revert happens in EndAllLocals.
//
if (CurrentBatchFile->hRestrictedToken != NULL) {
if (!ImpersonateLoggedOnUser(CurrentBatchFile->hRestrictedToken)) {
//
// We failed to impersonate. Close the token handle and
// return failure.
//
CloseHandle(CurrentBatchFile->hRestrictedToken);
CurrentBatchFile->hRestrictedToken = NULL;
return(FAILURE);
}
}
} else {
DWORD dwLastError = GetLastError();
// if (FailAlways) dwLastError = ERROR_ACCESS_DISABLED_BY_POLICY;
if (dwLastError == ERROR_ACCESS_DISABLED_BY_POLICY) {
SaferRecordEventLogEntry(
Level,
(WCHAR *) fp,
NULL);
PutStdErr(ERROR_ACCESS_DISABLED_BY_POLICY, NOARGS );
}
//
// We failed to compute the restricted token from the authorization level.
// We will not run the batchfile.
//
CurrentBatchFile->hRestrictedToken = NULL;
SaferCloseLevel(Level);
return(FAILURE);
}
} else {
//
// In case of errors, return failure.
//
return(FAILURE);
}
} except (LastRetCode = GetExceptionCode( ), EXCEPTION_EXECUTE_HANDLER) {
if (LastRetCode != VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND)) {
ReportDelayLoadErrors = TRUE;
return FAILURE;
}
}
ReportDelayLoadErrors = TRUE;
CurrentBatchFile->filepos = 0; // Zero position pointer
CurrentBatchFile->filespec = fp; // Insure correct str
//
// If extensions are enabled and the command line begins with
// a COLON then we got here via CALL :label, so update our
// CurrentBatchFile file spec with our parents file spec, since we are
// in the same file.
//
if (fEnableExtensions && *n->cmdline == COLON) {
struct batdata *bdat;
bdat = CurrentBatchFile->backptr;
mystrcpy(CurrentBatchFile->filespec, bdat->filespec);
CurrentBatchFile->filepos = bdat->filepos;
} else {
//
// Otherwise old behavior is going to a new file. Get its full name
//
if (FullPath(CurrentBatchFile->filespec, TmpBuf,MAX_PATH)) /* If bad name, */
return(FAILURE); /* ...return failure */
}
mystrcpy(TmpBuf, n->cmdline); /* Preserve cmdline and */
*(s = TmpBuf+mystrlen(TmpBuf)+1) = NULLC; /* ...argstr in case this */
if (n->argptr)
mystrcpy(s, n->argptr); /* ...is a chain and node */
FreeStack(CurrentBatchFile->stackmin); /* ...gets lost here */
DEBUG((BPGRP,BPLVL,"SETBAT: fspec = `%ws'",CurrentBatchFile->filespec));
DEBUG((BPGRP,BPLVL,"SETBAT: orgargs = `%ws'",s));
DEBUG((BPGRP,BPLVL,"SETBAT: Making arg0 string"));
CurrentBatchFile->alens[0] = mystrlen(TmpBuf);
CurrentBatchFile->aptrs[0] = mkstr( (CurrentBatchFile->alens[0]+1) * sizeof( TCHAR ) );
if (CurrentBatchFile->aptrs[0] == NULL) {
PutStdErr(MSG_NO_MEMORY, NOARGS );
return(FAILURE);
}
mystrcpy(CurrentBatchFile->aptrs[0], TmpBuf);
CurrentBatchFile->orgaptr0 = CurrentBatchFile->aptrs[0];
DEBUG((BPGRP, BPLVL, "SETBAT: arg 0 = %ws", CurrentBatchFile->aptrs[0]));
DEBUG((BPGRP, BPLVL, "SETBAT: len 0 = %d", CurrentBatchFile->alens[0]));
DEBUG((BPGRP, BPLVL, "SETBAT: Zeroing remaining arg elements"));
for (i = 1; i < 10; i++) { /* Zero any previous */
CurrentBatchFile->aptrs[i] = 0; /* ...arg pointers and */
CurrentBatchFile->alens[i] = 0; /* ...length values */
}
if (*s) {
DEBUG((BPGRP,BPLVL,"SETBAT: Making orgargs string"));
CurrentBatchFile->orgargs = mkstr( (mystrlen( s ) + 1) * sizeof( TCHAR ) );
if (CurrentBatchFile->orgargs == NULL) {
PutStdErr(MSG_NO_MEMORY, NOARGS );
return(FAILURE);
}
//
// Strip leading spaces from orgargs
//
s += _tcsspn( s, TEXT( " \t" ));
mystrcpy( CurrentBatchFile->orgargs, s );
//
// Strip trailing spaces from orgargs
//
s = CurrentBatchFile->orgargs + mystrlen( CurrentBatchFile->orgargs );
while (s != CurrentBatchFile->orgargs) {
if (s[-1] != TEXT( ' ' ) && s[-1] != TEXT( '\t' )) {
break;
}
s--;
}
*s = TEXT( '\0' );
if (!fEnableExtensions) {
//
// /Q on batch script invocation only supported when extensions disabled
//
s = CurrentBatchFile->orgargs;
while (s = mystrchr(s, SwitChar)) {
if (_totupper(*(++s)) == QUIETCH) {
EchoFlag = E_OFF;
mystrcpy(s-1,s+1);
DEBUG((BPGRP,BPLVL,"SETBAT: Found Q switch, orgargs now = %ws",CurrentBatchFile->orgargs));
break;
}
}
}
DEBUG((BPGRP,BPLVL,"SETBAT: Tokenizing orgargs string"));
s = TokStr(CurrentBatchFile->orgargs, NULL, TS_NOFLAGS);
for (i = 1; *s && i < 10; s += j+1, i++) {
CurrentBatchFile->aptrs[i] = s;
CurrentBatchFile->alens[i] = j = mystrlen(s);
DEBUG((BPGRP, BPLVL, "SETBAT: arg %d = %ws", i, CurrentBatchFile->aptrs[i]));
DEBUG((BPGRP, BPLVL, "SETBAT: len %d = %d", i, CurrentBatchFile->alens[i]));
}
CurrentBatchFile->args = s;
} else {
DEBUG((BPGRP, BPLVL, "SETBAT: No args found, ptrs = 0"));
CurrentBatchFile->orgargs = CurrentBatchFile->args = NULL;
}
CurrentBatchFile->stacksize = DCount; /* Protect from parser */
DEBUG((BPGRP, BPLVL, "SETBAT: Stack set: Min = %d, size = %d",CurrentBatchFile->stackmin,CurrentBatchFile->stacksize));
return(SUCCESS);
}
/*** DisplayStatement - controls the displaying of batch file statements
*
* Purpose:
* Walk a parse tree to display the statement contained in it.
* If n is null, the node contains a label, or the node is SILTYP
* and flg is DSP_SIL, do nothing.
*
* void DisplayStatement(struct node *n, int flg)
*
* Args:
* n - pointer to root of the parse tree
* flg - flag indicates "silent" or "verbose" mode
*
*/
void DisplayStatement(n, flg)
struct node *n;
int flg; /* M019 - New flag argument */
{
TCHAR *eqstr = TEXT("");
void DisplayOperator(),
DisplayRedirection(); /* M008 - Made void */
/* M019 - Added extra conditionals to determine whether or not to display
* any part of the tree that following a SILent node. This is done
* based on a new flag argument which indicates SILENT or VERBOSE
* mode (DSP_SIL or DSP_VER).
* NOTE: When this routine is combined with pipes to xfer statements
* to a child Command.com via STDOUT, it will have to be changed in
* order to discriminate between the two purposes for which it is
* called. Flag definitions already exist in CMD.H for this purpose
* (DSP_SCN & DSP_PIP).
*/
if (!n ||
(n->type == SILTYP && flg == DSP_SIL) ||
((((struct cmdnode *) n)->cmdline) &&
*(((struct cmdnode *) n)->cmdline) == COLON))
return;
switch (n->type) {
case LFTYP:
DisplayOperator(n, CrLf);
break;
case CSTYP:
DisplayOperator(n, CSSTR);
break;
case ORTYP:
DisplayOperator(n, ORSTR);
break;
case ANDTYP:
DisplayOperator(n, ANDSTR);
break;
case PIPTYP:
DisplayOperator(n, PIPSTR);
break;
case SILTYP:
CmdPutString( SILSTR );
DisplayStatement(n->lhs, DSP_VER);
DisplayRedirection(n);
break;
case PARTYP:
DEBUG((BPGRP, BPLVL, "DST: Doing parens"));
CmdPutString( LEFTPSTR );
if (n->lhs->type == LFTYP)
cmd_printf( CrLf );
DisplayStatement(n->lhs, DSP_SIL); /* M019 */
if (n->lhs->type == LFTYP)
cmd_printf( CrLf );
cmd_printf(Fmt11, RPSTR); /* M013 */
DisplayRedirection(n);
break;
case FORTYP:
DEBUG((BPGRP, BPLVL, "DST: Displaying FOR."));
//
// If extensions are enabled, handle displaying the new
// optional switches on the FOR statement.
//
if (fEnableExtensions) {
cmd_printf(TEXT("%.3s"), ((struct fornode *) n)->cmdline);
if (((struct fornode *)n)->flag & FOR_LOOP)
cmd_printf(TEXT(" %s"), ForLoopStr);
else
if (((struct fornode *)n)->flag & FOR_MATCH_DIRONLY)
cmd_printf(TEXT(" %S"), ForDirTooStr);
else
if (((struct fornode *)n)->flag & FOR_MATCH_PARSE) {
cmd_printf(TEXT(" %s"), ForParseStr);
if (((struct fornode *)n)->parseOpts)
cmd_printf(TEXT(" %s"), ((struct fornode *)n)->parseOpts);
} else
if (((struct fornode *)n)->flag & FOR_MATCH_RECURSE) {
cmd_printf(TEXT(" %s"), ForRecurseStr);
if (((struct fornode *)n)->recurseDir)
cmd_printf(TEXT(" %s"), ((struct fornode *)n)->recurseDir );
}
cmd_printf(TEXT(" %s "), ((struct fornode *) n)->cmdline+_tcslen(ForStr)+1);
} else
cmd_printf(Fmt11, ((struct fornode *) n)->cmdline);
cmd_printf(Fmt13, ((struct fornode *) n)->arglist, ((struct fornode *) n)->cmdline+DOPOS);
/* M019 */DisplayStatement(((struct fornode *) n)->body, DSP_VER);
break;
case IFTYP:
DEBUG((BPGRP, BPLVL, "DST: Displaying IF."));
cmd_printf(Fmt11, ((struct ifnode *) n)->cmdline); /* M013 */
//
// If extensions are enabled, handle displaying the new
// optional /I switch on the IF statement.
//
if (fEnableExtensions) {
if (((struct ifnode *)n)->cond->type != NOTTYP) {
if (((struct ifnode *)n)->cond->flag == CMDNODE_FLAG_IF_IGNCASE)
cmd_printf(TEXT("/I "));
} else
if (((struct cmdnode *)(((struct ifnode *)n)->cond->argptr))->flag == CMDNODE_FLAG_IF_IGNCASE)
cmd_printf(TEXT("/I "));
}
/* M019 */DisplayStatement((struct node *)(((struct ifnode *) n)->cond), DSP_SIL);
/* M019 */DisplayStatement(((struct ifnode *) n)->ifbody, DSP_SIL);
if (((struct ifnode *) n)->elsebody) {
cmd_printf(Fmt02, ((struct ifnode *) n)->elseline); /* M013 */
/* M019 */DisplayStatement(((struct ifnode *) n)->elsebody, DSP_SIL);
}
break;
case NOTTYP:
DEBUG((BPGRP, BPLVL, "DST: Displaying NOT."));
/* M002 - Removed '\n' from printf statement below.
*/
cmd_printf(Fmt11, ((struct cmdnode *) n)->cmdline); /* M013 */
/* M002 ends */
/* M019 */DisplayStatement((struct node *)(((struct cmdnode *) n)->argptr), DSP_SIL);
break;
case STRTYP:
case CMPTYP:
eqstr = TEXT("== ");
//
// If extensions are enabled, handle displaying the
// new forms of comparison operators.
//
if (fEnableExtensions) {
if (((struct cmdnode *) n)->cmdarg == CMDNODE_ARG_IF_EQU)
eqstr = TEXT("EQU ");
else
if (((struct cmdnode *) n)->cmdarg == CMDNODE_ARG_IF_NEQ)
eqstr = TEXT("NEQ ");
else
if (((struct cmdnode *) n)->cmdarg == CMDNODE_ARG_IF_LSS)
eqstr = TEXT("LSS ");
else
if (((struct cmdnode *) n)->cmdarg == CMDNODE_ARG_IF_LEQ)
eqstr = TEXT("LEQ ");
else
if (((struct cmdnode *) n)->cmdarg == CMDNODE_ARG_IF_GTR)
eqstr = TEXT("GTR ");
else
if (((struct cmdnode *) n)->cmdarg == CMDNODE_ARG_IF_GEQ)
eqstr = TEXT("GEQ ");
}
cmd_printf(Fmt12, ((struct cmdnode *) n)->cmdline, eqstr, ((struct cmdnode *) n)->argptr); /* M013 */
break;
case ERRTYP:
case EXSTYP:
case CMDVERTYP:
case DEFTYP:
cmd_printf(Fmt15, ((struct cmdnode *) n)->cmdline, ((struct cmdnode *) n)->argptr); /* M013 */
break;
case REMTYP: /* M009 - Rem now seperate type */
case CMDTYP:
DEBUG((BPGRP, BPLVL, "DST: Displaying command."));
CmdPutString( ((struct cmdnode *) n)->cmdline );
if (((struct cmdnode *) n)->argptr)
cmd_printf(Fmt11, ((struct cmdnode *) n)->argptr); /* M013 */
DisplayRedirection(n);
}
}
/*** DisplayOperator - controls displaying statments containing operators
*
* Purpose:
* Diplay an operator and recurse on its left and right hand sides.
*
* void DisplayOperator(struct node *n, TCHAR *opstr)
*
* Args:
* n - node of operator to be displayed
* opstr - the operator to print
*
*/
void DisplayOperator(n, opstr)
struct node *n;
TCHAR *opstr;
{
void DisplayStatement(); /* M008 - made void */
DEBUG((BPGRP, BPLVL, "DOP"));
DisplayStatement(n->lhs, DSP_SIL); /* M019 */
if (n->rhs) {
cmd_printf(Fmt02, opstr);
DisplayStatement(n->rhs, DSP_SIL); /* M019 */
}
}
/*** DisplayRedirection - displays statements' I/O redirection
*
* Purpose:
* Display the type and file names of any redirection associated with
* this node.
*
* void DisplayRedirection(struct node *n)
*
* Args:
* n - the node to check for redirection
*
* Notes:
* M017 - This function has been extensively modified to conform
* to new data structures for redirection.
* M018 - Modified for redirection of handles other than 0 for input.
*/
void DisplayRedirection(n)
struct node *n;
{
struct relem *tmp;
DEBUG((BPGRP, BPLVL, "DRD"));
tmp = n->rio;
while (tmp) {
cmd_printf(Fmt18, TEXT('0')+tmp->rdhndl, tmp->rdop);
if (tmp->flag)
cmd_printf(Fmt20);
cmd_printf(Fmt11, tmp->fname);
tmp = tmp->nxt;
}
}
/*** OpenPosBat - open a batch file and position its file pointer
*
* Purpose:
* Open a batch file and position the file pointer to the location at
* which the next statement is to be read.
*
* int OpenPosBat(struct batdata *bdat)
*
* Args:
* bdat - pointer to current batch job structure
*
* Returns:
* The handle of the file if everything is successful. Otherwise,
* FAILURE.
*
* Notes:
* M033 - Now reports sharing violation errors if appropriate.
*
*/
CRTHANDLE OpenPosBat(bdat)
struct batdata *bdat;
{
CRTHANDLE fh; /* Batch file handle */
int DriveIsFixed();
DEBUG((BPGRP, BPLVL, "OPB: fspec = %ws", bdat->filespec));
while ((fh = Copen(bdat->filespec, O_RDONLY|O_BINARY)) == BADHANDLE) {
if (DosErr != ERROR_FILE_NOT_FOUND) { /* M037 */
PrtErr(ERROR_OPEN_FAILED); /* M037 */
return(fh);
} else if ( DriveIsFixed( bdat->filespec ) ) { /* @@4 */
PutStdErr( MSG_CMD_BATCH_FILE_MISSING, NOARGS); /* @@4 */
return(fh); /* @@4 */
} else {
PutStdErr(MSG_INSRT_DISK_BAT, NOARGS);
if (0x3 == _getch()) {
SetCtrlC();
return(fh);
}
}
}
SetFilePointer(CRTTONT(fh), bdat->filepos, NULL, FILE_BEGIN);
return(fh);
}
/*** eEcho - execute an Echo command
*
* Purpose:
* To either print a message, change the echo status, or display the
* echo status.
*
* int eEcho(struct cmdnode *n)
*
* Args:
* n - the parse tree node containing the echo command
*
* Returns:
* SUCCESS always.
*
*/
int eEcho(
struct cmdnode *n
)
{
int oocret;
int rc;
DEBUG((BPGRP, OTLVL, "eECHO: Entered."));
switch (oocret = OnOffCheck(n->argptr, OOC_NOERROR)) {
case OOC_EMPTY:
rc = PutStdOut(((EchoFlag == E_ON) ? MSG_ECHO_ON : MSG_ECHO_OFF), NOARGS);
if (rc != 0) {
if (FileIsPipe(STDOUT)) {
PutStdErr( MSG_CMD_INVAL_PIPE, NOARGS );
} else if ( !FileIsDevice( STDOUT ) ) {
PutStdErr( rc, NOARGS );
} else if (!(flgwd & 2)) {
PutStdErr( ERROR_WRITE_FAULT, NOARGS );
}
}
break;
case OOC_OTHER:
cmd_printf(Fmt17, n->argptr+1);
break;
default:
EchoFlag = oocret;
}
return(SUCCESS);
}
/*** eFor - controls the execution of a For loop
*
* Purpose:
* Loop through the elements in a FOR loop arg list. Expand those that
* contain wildcards.
*
* int eFor(struct fornode *n)
*
* Args:
* n - the FOR loop parse tree node
*
* Returns:
* The retcode of the last command executed in the FOR body.
*
* Notes:
* *** IMPORTANT ***
* Each iteration through the FOR loop being executed causes more memory
* to be allocated. This can cause Command to run out of memory. To
* keep this from happening, we use DCount to locate the end of the data
* stack after the first iteration through the FOR loop. At the end of
* each successive iteration through the loop, memory is freed that was
* allocated during that iteration of the loop. The first iterations'
* memory is NOT freed because there is data allocated there that must
* be kept for successive iterations; namely, the save structure in the
* for loop node.
*
*/
void FvarRestore()
{
if ( FvarsSaved ) { /* @@ */
FvarsSaved = FALSE; /* @@ */
Fvars = save_Fvars; /* @@ */
Fsubs = save_Fsubs; /* @@ */
} /* @@ */
}
FRecurseWork(
TCHAR *path,
TCHAR *filepart,
struct fornode *pForNode,
PCPYINFO fsinfo,
int i,
TCHAR *argtoks
);
FParseWork(
struct fornode *pForNode,
int i,
BOOL bFirstLoop
);
FLoopWork(
struct fornode *pForNode,
PCPYINFO fsinfo,
int i,
TCHAR *argtoks,
BOOL bFirstLoop
);
int eFor(struct fornode *pForNode)
{
TCHAR *argtoks; /* Tokenized argument list */
int i = 0; /* Temp */
int datacount; /* Elts on data stack not to free */
int forretcode = SUCCESS;
/*509*/int argtoklen;
BOOL bFirstLoop;
PCPYINFO fsinfo; /* Used for expanded fspec */
FvarsSaved = FALSE; /* @@ */
bFirstLoop = TRUE;
fsinfo = (PCPYINFO) mkstr(sizeof(CPYINFO));
if (!fsinfo) {
PutStdErr(MSG_NO_MEMORY, NOARGS );
return(FAILURE);
}
if (Fvars) {
Fvars = (TCHAR*)resize(Fvars,((i = mystrlen(Fvars))+2)*sizeof(TCHAR));
Fsubs = (TCHAR **)resize(Fsubs,(i+1)*(sizeof(TCHAR *)) );
} else {
Fvars = (TCHAR*)mkstr(2*sizeof(TCHAR)); /* If no str, make one */
Fsubs = (TCHAR **)mkstr(sizeof(TCHAR *)); /* ...also a table */
}
if (Fvars == NULL || Fsubs == NULL) {
PutStdErr(MSG_NO_MEMORY, NOARGS );
return FAILURE;
}
Fvars[i] = (TCHAR)(pForNode->forvar); /* Add new var to str */
Fvars[i+1] = NULLC;
//
// Check for the new forms of the FOR loop. None of these flags
// will be set if extensions are not enabled
//
if (pForNode->flag & FOR_LOOP) {
TCHAR ForLoopBuffer[32];
int ForLoopValue, ForLoopStep, ForLoopLimit;
//
// Handle the loop for of the FOR statement, where the set
// is described by a starting number and step value (+ or -)
// and an end number
//
// FOR /L %i in (start,step,end) do
//
argtoks = TokStr(pForNode->arglist, NULL, TS_NOFLAGS);
ForLoopValue = _tcstol( argtoks, NULL, 0 );
argtoklen = mystrlen( argtoks );
argtoks += argtoklen+1;
ForLoopStep = _tcstol( argtoks, NULL, 0 );
argtoklen = mystrlen( argtoks );
argtoks += argtoklen+1;
ForLoopLimit = _tcstol( argtoks, NULL, 0 );
//
// We have the three numbers, now run the body of the FOR
// loop with each value described
//
datacount = 0;
while (TRUE) {
//
// If step is negative, go until loop value is less
// than limit. Otherwise go until it is greater than
// limit.
//
if (ForLoopStep < 0) {
if (ForLoopValue < ForLoopLimit)
break;
} else {
if (ForLoopValue > ForLoopLimit)
break;
}
FvarRestore();
DEBUG((BPGRP, FOLVL, "FOR: element %d = `%ws'",i ,argtoks));
CheckCtrlC();
//
// Convert the loop value to text and set the value of the loop
// variable
//
_sntprintf(ForLoopBuffer, 32, TEXT("%d"), ForLoopValue);
Fsubs[i] = ForLoopBuffer;
//
// Run the body of the FOR Loop
//
forretcode = FWork(pForNode->body,bFirstLoop);
datacount = ForFree(datacount);
bFirstLoop = FALSE;
//
// Step to next value
//
ForLoopValue += ForLoopStep;
}
} else
if (pForNode->flag & FOR_MATCH_PARSE) {
//
// Handle the new parse form of the FOR loop
//
// FOR /F "parameters" %i in (filelist) do ...
// FOR /F "parameters" %i in (`command to execute`) do ...
// FOR /F "parameters" %i in ('literal string') do ...
//
forretcode = FParseWork(pForNode,
i,
TRUE
);
} else
if (pForNode->flag & FOR_MATCH_RECURSE) {
TCHAR pathbuf[MAX_PATH];
TCHAR *filepart;
TCHAR *p;
DWORD Length;
//
// Handle the new recurse form of the FOR loop
//
// FOR /R directory %i in (filespecs) do ...
//
// Where directory is an optional directory path of where to start
// walking the directory tree. Default is the current directory.
// filespecs is one or more file name specifications, wildcards
// allowed.
//
//
// Get the full path of the directory to start walking, defaulting
// to the current directory.
//
p = StripQuotes( pForNode->recurseDir ? pForNode->recurseDir : TEXT(".\\"));
Length = GetFullPathName( p, MAX_PATH, pathbuf, &filepart );
if (Length == 0 || Length >= MAX_PATH ) {
PutStdErr( MSG_FULL_PATH_TOO_LONG, ONEARG, p );
forretcode = FAILURE;
} else {
if (filepart == NULL) {
filepart = lastc(pathbuf);
if (*filepart != BSLASH) {
*++filepart = BSLASH;
}
*++filepart = NULLC;
} else {
//
// A directory is present. Append a path sep
//
mystrcat( pathbuf, TEXT( "\\" ));
filepart = lastc( pathbuf ) + 1;
}
//
// Tokenize the list of file specifications
//
argtoks = TokStr(pForNode->arglist, NULL, TS_NOFLAGS);
//
// Do the work
//
forretcode = FRecurseWork(pathbuf, filepart, pForNode, fsinfo, i, argtoks);
}
} else {
//
// If none of the new flags specified, then old style FOR statement
// Tokenize the elements of the set and loop over them
//
argtoks = TokStr(pForNode->arglist, NULL, TS_NOFLAGS);
DEBUG((BPGRP, FOLVL, "FOR: initial argtok = `%ws'", argtoks));
forretcode = FLoopWork(pForNode, fsinfo, i, argtoks, TRUE);
DEBUG((BPGRP, FOLVL, "FOR: Exiting."));
}
//
// All done, deallocate the FOR variable
//
if (i) {
if (Fvars || (*Fvars)) {
*(Fvars+mystrlen(Fvars)-1) = NULLC;
}
Fsubs[i] = NULL;
} else {
Fvars = NULL;
Fsubs = NULL;
}
return(forretcode);
}
/*** FRecurseWork - controls the execution of a For loop with the /R option
*
* Purpose:
* Execute a FOR loop statement for recursive walk of a directory tree
*
* FRecurseWork(TCHAR *path, TCHAR *filepart,
* struct fornode *pForNode, PCPYINFOfsinfo,
* int i, TCHAR *argtoks)
*
* Args:
* path - full path of directory to start recursing down
* filepart - tail portion of full path where file name portion is
* pForNode - pointer to the FOR parse tree node
* fsinfo - work buffer for expanding file specification wildcards
* i - FOR variable index in Fvars and Fsubs arrays
* argtoks - the tokenized data set to loop over. This set is presumed
* to be file names with possible wild cards. This set is
* evaluated for each directory seen by the recusive walk of the
* the directory tree. So FOR /R "." %i in (*.c *.h) do echo %i
* would echo all the .c and .h files in a directory tree
*
* Returns:
* The retcode of the last statement executed in the for body or FORERROR.
*
*/
FRecurseWork(
TCHAR *path,
TCHAR *filepart,
struct fornode *pForNode,
PCPYINFO fsinfo,
int i,
TCHAR *argtoks
)
{
WIN32_FIND_DATA buf; /* Buffer for find first/next */
HANDLE hnFirst; /* handle from ffirst() */
int forretcode = FORERROR;
int npfxlen, ntoks;
TCHAR *s1;
TCHAR *s2;
TCHAR *tmpargtoks;
//
// Calculate the length of the path and find the end of the
// tokenized data set and the number of tokens in the set.
//
npfxlen = _tcslen(path);
ntoks = 0;
s1 = argtoks;
while (*s1) {
ntoks += 1;
while (*s1++) {
NOTHING;
}
}
//
// Now allocate space for a copy of the tokenized data set with room to prefix
// each element of the set with the path string. Construct the copy of the set
//
tmpargtoks = mkstr( ntoks * ((npfxlen + ((int)(s1 - argtoks) + 1)) * sizeof(TCHAR)) );
if (tmpargtoks == NULL) {
PutStdErr(MSG_NO_MEMORY, NOARGS );
return FAILURE;
}
s1 = argtoks;
s2 = tmpargtoks;
while (*s1) {
_tcsncpy(s2, path, npfxlen);
_tcscpy(s2+npfxlen, s1);
s2 += npfxlen;
while (*s1++)
s2 += 1;
s2 += 1;
}
*s2++ = NULLC;
//
// Now run the body of the FOR loop with the new data set, then free it.
//
forretcode = FLoopWork(pForNode, fsinfo, i, tmpargtoks, TRUE);
//
// Now find any subdirectories in path and recurse on them
//
filepart[0] = STAR;
filepart[1] = NULLC;
hnFirst = FindFirstFile( path, &buf );
filepart[0] = NULLC;
if (hnFirst != INVALID_HANDLE_VALUE) {
do {
_tcscpy(filepart, buf.cFileName);
if (buf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY &&
_tcscmp(buf.cFileName, TEXT(".")) &&
_tcscmp(buf.cFileName, TEXT(".."))) {
s1 = lastc(filepart);
*++s1 = BSLASH;
*++s1 = NULLC;
forretcode = FRecurseWork(path, s1, pForNode, fsinfo, i, argtoks);
}
} while (FindNextFile( hnFirst, &buf ));
FindClose(hnFirst);
}
return(forretcode);
}
/*** FParseWork - controls the execution of a For loop with the /F option
*
* Purpose:
* Execute a FOR loop statement for parsing the contents of a file
*
* FParseWork(struct fornode *pForNode, PCPYINFOfsinfo,
* int i, TCHAR *argtoks, BOOL bFirstLoop)
*
* Args:
* pForNode - pointer to the FOR parse tree node
* i - FOR variable index in Fvars and Fsubs arrays
* bFirstLoop - TRUE if first time through loop
*
* Returns:
* The retcode of the last statement executed in the for body or FORERROR.
*
*/
FParseWork(
struct fornode *pForNode,
int i,
BOOL bFirstLoop
)
{
HANDLE hFile; /* handle from ffirst() */
DWORD dwFileSize, dwBytesRead;
int datacount; /* Elts on data stack not to free */
int argtoklen;
int forretcode = FORERROR;
TCHAR *argtoks;
TCHAR *s1;
TCHAR *s2;
TCHAR *sToken;
TCHAR *sEnd;
TCHAR *tmpargtoks = NULL;
TCHAR eol=TEXT(';');
TCHAR quoteChar;
TCHAR *delims;
TCHAR chCmdLine, chLiteralString;
int nVars;
int nSkip;
int nSkipSave;
int nTok, nTokEnd, nTokBits, nTokStar;
DWORD nTokenMask;
BOOL bNewSemantics;
//
// First see if we have any parse options present. Possible parse options are:
//
// eol=c // c is the end of line comment character
// delims=cccc // cccc specifies one or more delimeter characters
// skip=n // n specifies how many lines at the begin of each file
// // to skip (defaults to zero).
// tokens=m,n-o // m is a token number to pass to the body of the FOR loop
// // n-o is a range of token numbers to pass. (defaults
// // to tokens=1
// usebackq // If present, allows new back quotes for command line,
// // single quote for literal strings, which frees up double
// // quotes for quoting file names
//
//
delims = (TCHAR *) gmkstr( (_tcslen( DEFAULT_DELIMS ) + 1) * sizeof( TCHAR ) );
mystrcpy( delims, DEFAULT_DELIMS );
nSkip = 0;
nVars = 1;
nTokenMask = 1;
nTokStar = 0;
bNewSemantics = FALSE;
if (pForNode->parseOpts) {
s1 = pForNode->parseOpts;
if (*s1 == QUOTE || *s1 == TEXT('\'')) {
quoteChar = *s1++;
} else {
quoteChar = NULLC;
}
nTokBits = 1;
while (s1 && *s1) {
while (*s1 && *s1 <= SPACE)
s1 += 1;
if (*s1 == quoteChar)
break;
if (!_tcsnicmp(s1, TEXT("usebackq"), 8)) {
bNewSemantics = TRUE;
s1 += 8;
} else
if (!_tcsnicmp(s1, TEXT("useback"), 7)) {
bNewSemantics = TRUE;
s1 += 7;
} else
if (!_tcsnicmp(s1, TEXT("eol="), 4)) {
eol=s1[4];
s1 += 5;
} else
if (!_tcsnicmp(s1, TEXT("delims="), 7)) {
s1 += 7;
s2 = s1;
//
// Advance to the next space or end of string
//
while (*s1 && *s1 != quoteChar) {
if (*s1 == SPACE && s1[1] != quoteChar)
break;
else
s1 += 1;
}
//
// New delimiter characters
//
FreeStr( delims );
delims = (TCHAR *) gmkstr( ((int)(s1 - s2) + 1) * sizeof( TCHAR ));
_tcsncpy(delims, s2, (UINT)(s1-s2));
delims[s1-s2] = NULLC;
if (*s1)
s1 += 1;
} else
if (!_tcsnicmp(s1, TEXT("skip="), 5)) {
s1 += 5;
nSkip = _tcstol(s1, &s1, 0);
if (nSkip <= 0)
goto badtokens;
} else
if (!_tcsnicmp(s1, TEXT("tokens="), 7)) {
s1 += 7;
nTokenMask = 0;
nTokBits = 0;
while (*s1 && *s1 != quoteChar) {
if (*s1 == STAR) {
s1 += 1;
nTokBits += 1;
nTokStar = nTokBits;
break;
}
nTok = _tcstol(s1, &s1, 0);
if (nTok <= 0)
goto badtokens;
if (*s1 == MINUS) {
nTokEnd = _tcstol(s1+1, &s1, 0);
if (nTokEnd <= 0)
goto badtokens;
} else
nTokEnd = nTok;
if (nTok > 0 && nTokEnd < 32)
while (nTok <= nTokEnd) {
nTokBits += 1;
nTokenMask |= 1 << (nTok - 1);
nTok += 1;
}
if (*s1 == COMMA)
s1 += 1;
else
if (*s1 != STAR)
break;
}
if (nTokBits > nVars)
nVars = nTokBits;
} else {
badtokens:
PutStdErr(MSG_SYNERR_GENL,ONEARG,s1);
return(FAILURE);
}
}
//
// If user specified more than one token then we need to allocate
// additional FOR variable names to pass them to the body of the
// FOR loop. The variables names are the next nVars-1 letters after
// the one the user specified in the FOR statement. So if they specified
// %i as the variable name and requested 3 tokens, then %j and %k would
// be allocated here.
//
if (nVars > 1) {
Fvars = (TCHAR*)resize(Fvars,(i+nVars)*sizeof(TCHAR) );
Fsubs = (TCHAR **)resize(Fsubs,(i+nVars)*sizeof(TCHAR *) );
if (Fvars == NULL || Fsubs == NULL) {
PutStdErr(MSG_NO_MEMORY, NOARGS );
Abort( );
}
for (nTok=1; nTok<nVars; nTok++) {
Fvars[i+nTok] = (TCHAR)(pForNode->forvar+nTok);
Fsubs[i+nTok] = NULL;
}
Fvars[i+nTok] = NULLC;
}
}
//
// Parse string between parenthesis. Only parse it if present and
// not either the Command Line or Literal String mode
//
argtoks = pForNode->arglist;
if (bNewSemantics) {
chCmdLine = TEXT('`');
chLiteralString = TEXT('\'');
} else {
chCmdLine = TEXT('\'');
chLiteralString = QUOTE;
}
if (!argtoks || (*argtoks != chCmdLine && *argtoks != chLiteralString))
//
// If not the command line form, then tokenize the set of file names
//
argtoks = TokStr(argtoks, NULL, TS_NOFLAGS);
// Now loop over the set of files, opening and parsing each one.
//
nSkipSave = nSkip;
for (datacount = 0; *argtoks && !GotoFlag; argtoks += argtoklen+1) {
FvarRestore();
CheckCtrlC();
s1 = sEnd = NULL;
tmpargtoks = NULL;
nSkip = nSkipSave;
argtoklen = mystrlen( argtoks );
if (*argtoks == chCmdLine && argtoklen > 1 && argtoks[argtoklen-1] == chCmdLine) {
FILE *pChildOutput;
char *spBegin;
size_t cbUsed, cbTotal;
//
// If the file name is a quoted string, with single quotes, then it is a command
// line to execute. So strip off the quotes. Note that the for() loop expects
// there to be a double NUL at the end. However, we DON'T have such a thing here
// since we never called TokStr. We fake this by putting in a NUL to terminate
// the command line and then lie about the length
//
argtoks += 1;
argtoklen -= 2;
argtoks[argtoklen] = NULLC;
//
// Execute the command line, getting a handle to its standard output
// stream.
//
pChildOutput = _tpopen( argtoks, TEXT( "rb" ));
if (pChildOutput == NULL) {
PutStdErr(MSG_DIR_BAD_COMMAND_OR_FILE, ONEARG, argtoks);
return(GetLastError());
}
//
// Now read the standard output stream, collecting it into allocated
// memory so we can parse it when the command finishes. Read until
// we hit EOF or an error on the child output handle.
//
cbUsed = cbTotal = 0;
spBegin = NULL;
while (!feof(pChildOutput) && !ferror(pChildOutput)) {
if ((cbTotal-cbUsed) < 512) {
cbTotal += 256;
if (spBegin)
spBegin = resize(spBegin, cbTotal);
else
spBegin = mkstr(cbTotal);
if (spBegin == NULL) {
PutStdErr(MSG_NO_MEMORY, ONEARG, argtoks);
_pclose(pChildOutput);
return(ERROR_NOT_ENOUGH_MEMORY);
}
}
spBegin[cbUsed] = TEXT( '\0' );
if (!fgets(spBegin+cbUsed, (int)(cbTotal-cbUsed), pChildOutput))
break;
cbUsed = strlen(spBegin);
}
//
// All done. Close the child output handle, which will actually wait
// for the child process to terminate.
//
_pclose(pChildOutput);
//
// Reallocate memory to what we actually need for the UNICODE representation
//
spBegin = resize(spBegin, (cbUsed+2) * sizeof(TCHAR));
if (Fvars == NULL || Fsubs == NULL) {
PutStdErr(MSG_NO_MEMORY, ONEARG, argtoks);
return(ERROR_NOT_ENOUGH_MEMORY);
}
//
// Move the ANSI data to the second half of the buffer so we can convert it
// to UNICODE
//
memmove(spBegin+cbUsed, spBegin, cbUsed);
tmpargtoks = (TCHAR *)spBegin;
dwFileSize = dwBytesRead = cbUsed;
//
// No go treat the in memory buffer we have created as if it were a
// file read in from disk.
//
goto gotfileinmemory;
} else if (*argtoks == chLiteralString
&& argtoklen > 1
&& argtoks[argtoklen-1] == chLiteralString) {
//
// If the file name is a literal string then it is an immediate
// string to be parsed. Fake things up for the parsing logic
// and fall through to it.
//
argtoks[argtoklen - 1] = NLN;
argtoks += 1;
argtoklen -= 2;
s1 = argtoks;
sEnd = s1 + argtoklen + 1;
} else {
if (*argtoks == QUOTE) {
argtoks += 1;
argtoklen -= 1;
s1 = lastc(argtoks);
if (*s1 == QUOTE) {
do {
*s1-- = NULLC;
}
while (s1 >= argtoks && *s1 == SPACE);
}
}
//
// We have an actual file name to try to open and read. So do it
//
hFile = CreateFile( argtoks,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
0,
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
PutStdErr(MSG_CMD_FILE_NOT_FOUND, ONEARG, argtoks);
return GetLastError();
} else {
BOOL b;
dwFileSize = SetFilePointer(hFile, 0, NULL, FILE_END);
SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
tmpargtoks = mkstr((dwFileSize+2) * sizeof( TCHAR ));
if (tmpargtoks == NULL) {
PutStdErr(MSG_NO_MEMORY, ONEARG, argtoks);
CloseHandle( hFile );
return(ERROR_NOT_ENOUGH_MEMORY);
}
dwBytesRead = 0xFFFFFFFF;
b = ReadFile( hFile,
#ifdef UNICODE
(LPSTR)tmpargtoks+dwFileSize,
#else
tmpargtoks,
#endif
dwFileSize,
&dwBytesRead,
NULL
);
CloseHandle(hFile);
if (!b) {
dwBytesRead = 0;
}
gotfileinmemory:
if (dwBytesRead == dwFileSize) {
//
// Successfully opened and read the data. Convert it to UNICODE
// and setup the variables for the parsing loop
//
#ifdef UNICODE
#ifdef FE_SB
dwFileSize =
#endif
MultiByteToWideChar(CurrentCP,
MB_PRECOMPOSED,
(LPSTR)tmpargtoks+dwFileSize,
dwFileSize,
tmpargtoks,
dwFileSize);
#endif
s1 = tmpargtoks;
sEnd = s1 + dwFileSize;
if (sEnd == s1 || sEnd[-1] != NLN)
*sEnd++ = NLN;
*sEnd = NULLC;
}
}
}
//
// This is the parsing loop
//
// s1 points to next character.
// sEnd points just after the last valid character to parse
//
// Loop isolates next line in input buffer, parse that line,
// Passes any tokens from the line to body of the FOR loop and
// then loops.
//
while (s1 < sEnd && !GotoFlag) {
CheckCtrlC();
//
// Not past the end of the buffer. Find the next
// newline
//
s1 = _tcschr(s2=s1, NLN);
//
// If no newline, then done parsing
//
if (s1 == NULL)
break;
//
// If CRLF, nuke the CR and the LF
//
if (s1 > s2 && s1[-1] == CR)
s1[-1] = NULLC;
*s1++ = NULLC;
//
// Done skipping input lines?
//
if (!nSkip) {
//
// Yes, parse this line
//
for (nTok=1; nTok<nVars; nTok++) {
Fsubs[i+nTok] = NULL;
}
nTok = 0;
nTokBits = 0;
//
// Null is the end of line marker now
//
while (*s2) {
//
// Skip any leading delimeters
//
while (*s2 && _tcschr(delims, *s2) != NULL)
s2++;
//
// If first character is eol comment character than
// skip this line
//
if (nTok == 0 && *s2==eol)
break;
//
// Remember start of token
//
sToken = s2;
if (nTokStar != 0 && (nTokBits+1) == nTokStar) {
Fsubs[i+nTokBits] = sToken;
nTokBits += 1;
break;
}
//
// Find the end of the token
//
while (*s2 && !_tcschr(delims, *s2))
s2 += 1;
//
// If we got a token, and it is not more than we can
// handle, then see if they want this token. If so,
// set the value of the appropriate FOR variable
//
if (sToken != s2 && nTok < 32) {
if ((nTokenMask & (1 << nTok++)) != 0) {
Fsubs[i+nTokBits] = sToken;
nTokBits += 1;
}
}
//
// If we're not at the end of the string, terminate this
// token and advance
//
if (*s2 != NULLC) {
*s2++ = NULLC;
}
}
//
// If we set any FOR variables, then run the body of the FOR loop
//
if (nTokBits) {
forretcode = FWork(pForNode->body,bFirstLoop);
datacount = ForFree(datacount);
bFirstLoop = FALSE;
}
} else
nSkip -= 1;
}
//
// If we allocated memory for the output of the command line, free it up
//
if (tmpargtoks != NULL) {
FreeStr( tmpargtoks );
tmpargtoks = NULL;
}
}
//
// If we used any additonal FOR variables, clear them here as we are done with them,
//
if (nVars > 1 && Fvars && (*Fvars)) {
Fvars[i+1] = NULLC;
Fsubs[i+1] = NULL;
}
return(forretcode);
}
/*** FLoopWork - controls the execution of a For loop
*
* Purpose:
* Execute a FOR loop statement for a given set
*
* FLoopWork(struct fornode *pForNode, PCPYINFOfsinfo, int i, TCHAR *argtoks, BOOL bFirstLoop
*
* Args:
* pForNode - pointer to the FOR parse tree node
* fsinfo - work buffer for expanding file specification wildcards
* i - FOR variable index in Fvars and Fsubs arrays
* argtoks - the tokenized data set to loop over
* bFirstLoop - TRUE if first time through loop
*
* Returns:
* The retcode of the last statement executed in the for body or FORERROR.
*
*/
FLoopWork(
struct fornode *pForNode,
PCPYINFO fsinfo,
int i,
TCHAR *argtoks,
BOOL bFirstLoop
)
{
TCHAR *forexpname; /* Used to hold expanded fspec */
WIN32_FIND_DATA buf; /* Buffer for find first/next */
HANDLE hnFirst; /* handle from ffirst() */
int datacount; /* Elts on data stack not to free */
int forretcode = SUCCESS;
int catspot; /* Add fnames to forexpname here */
int argtoklen;
DWORD forexpnamelen;
DWORD dwMatchAttributes;
//
// Loop, processing each string in the argtoks set
//
for (datacount = 0; *argtoks && !GotoFlag; argtoks += argtoklen+1) {
FvarRestore();
DEBUG((BPGRP, FOLVL, "FOR: element %d = `%ws'",i ,argtoks));
CheckCtrlC();
//
// Save the length of next string in set so we can skip over it
//
argtoklen = mystrlen( argtoks );
if (!(mystrchr(argtoks, STAR) || mystrchr(argtoks, QMARK))) {
//
// String contains no wildcard characters, so set the value of
// the FOR variable to the string and evaluate the body of the
// FOR loop
//
Fsubs[i] = argtoks;
forretcode = FWork(pForNode->body,bFirstLoop);
datacount = ForFree(datacount);
bFirstLoop = FALSE;
} else { /* Else, expand wildcards */
forexpnamelen = 0;
forexpname = NULL;
//
// String contains file specification wildcard characters.
// Expand the reference into one or more file or directory names,
// processing each name as a string
//
dwMatchAttributes = (pForNode->flag & FOR_MATCH_DIRONLY) ? A_AEVH : A_AEDVH;
mystrcpy( argtoks, StripQuotes( argtoks ) );
if (ffirst(argtoks, dwMatchAttributes, &buf, &hnFirst)) {
//
// Found at least one file. Parse it as a file name.
//
fsinfo->fspec = argtoks;
ScanFSpec(fsinfo);
//
// Remember where the file name portion is so we can append each
// matching file name to create a full path.
//
catspot = (fsinfo->pathend) ? (int)(fsinfo->pathend-fsinfo->fspec+1) : 0;
if (forexpnamelen < mystrlen(fsinfo->fspec)) {
forexpnamelen = mystrlen(fsinfo->fspec)+1;
if (forexpname == NULL)
forexpname = mkstr(forexpnamelen*sizeof(TCHAR));
else
forexpname = resize(forexpname, forexpnamelen*sizeof(TCHAR));
}
if (forexpname == NULL) {
PutStdErr( MSG_NO_MEMORY, NOARGS );
Abort( );
}
mystrcpy(forexpname, fsinfo->fspec);
do {
FvarRestore(); /* @@ */
//
// Copy current file name into full path buffer
//
if (forexpnamelen < (forexpnamelen+mystrlen(buf.cFileName))) {
forexpnamelen += mystrlen(buf.cFileName);
if (forexpname == NULL)
forexpname = mkstr(forexpnamelen*sizeof(TCHAR));
else
forexpname = resize(forexpname, forexpnamelen*sizeof(TCHAR));
if (forexpname == NULL) {
PutStdErr( MSG_NO_MEMORY, NOARGS );
Abort( );
}
}
mystrcpy(&forexpname[catspot], buf.cFileName);
//
// See if user wants files or directories and what we have
// and evaluate the body of the FOR loop if we have what the
// user wants. Ignore the bogus . and .. directory names
// returned by file systems.
//
if (!(pForNode->flag & FOR_MATCH_DIRONLY) ||
(buf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY &&
_tcscmp(buf.cFileName, TEXT(".")) &&
_tcscmp(buf.cFileName, TEXT("..")))) {
DEBUG((BPGRP, FOLVL, "FOR: forexpname = `%ws'", forexpname));
//
// User wants this file or directory name, so set
// the value of the FOR variable and evaluate the
// body of the FOR loop.
//
Fsubs[i] = forexpname;
forretcode = FWork(pForNode->body,bFirstLoop);
bFirstLoop = FALSE;
}
//
// Check for CtrlC and then get next matching file name
//
CheckCtrlC();
} while (fnext(&buf, dwMatchAttributes, hnFirst) && !GotoFlag);
datacount = ForFree(datacount);
//
// No more matching files, close the find handle
// Even though we've completed an iteration, we may NOT be the first
// pass through the loop since nothing may have been enumerated
//
findclose(hnFirst); /* @@4-@@M1 */
}
}
}
return(forretcode);
}
/*** FWork - controls the execution of 1 iteration of a For loop
*
* Purpose:
* Execute a FOR loop statement.
*
* FWork(struct node *n, TCHAR var, TCHAR *varval)
*
* Args:
* n - pointer to the body of the FOR loop
* bFirstLoop - TRUE if first time through loop
*
* Returns:
* The retcode of the last statement executed in the for body or FORERROR.
*
*/
FWork(n,bFirstLoop)
struct node *n;
BOOL bFirstLoop;
{
int forretcode; /* Dispatch Retcode or FORERROR */
void DisplayStatement(); /* M008 - made void */
DEBUG((BPGRP, FOLVL, "FW: Entered; Substituting variable"));
if (SubFor(n,bFirstLoop)) {
return(FORERROR);
} else {
DEBUG((BPGRP, FOLVL, "FW: EchoFlag = %d", EchoFlag));
if (EchoFlag == E_ON && n->type != SILTYP && !Necho) {
PrintPrompt();
DisplayStatement(n, DSP_SIL); /* M019 */
cmd_printf(CrLf); /* M026 */
}
forretcode = Dispatch(RIO_OTHER,n); /* M000 */
}
DEBUG((BPGRP, FOLVL, "FW: Returning %d", forretcode));
return(forretcode);
}
/*** SubFor - controls FOR variable substitutions
*
* Purpose:
* To walk a parse tree and make FOR variable substitutions on
* individual nodes. SFWork() is called to do individual string
* substitutions.
*
* int SubFor(struct node *n)
*
* Args:
* n - pointer to the statement subtree in which the substitutions are
* to be made
* bFirstLoop - TRUE if first time through loop
*
* Returns:
* SUCCESS if all goes well.
* FAILURE if an oversized command is found.
*
* Note:
* The variables to be substituted for are contained in Fvars and
* Fsubs is an array of string pointers to corresponding replacement
* strings. For I/O redirection, the list contained in the node
* must also be walked and its filespec strings examined.
*
*/
int SubFor(n,bFirstLoop)
struct node *n;
BOOL bFirstLoop;
{
int j; /* Temps used to make substitutions... */
struct relem *io; /* M017 - Pointer to redir list */
DEBUG((BPGRP, FOLVL, "SUBFOR: Entered."));
if (!n) {
DEBUG((BPGRP, FOLVL, "SUBFOR: Found NULL node."));
return(0);
}
switch (n->type) {
case LFTYP:
case CSTYP:
case ORTYP:
case ANDTYP:
case PIPTYP:
case PARTYP:
case SILTYP: /* M019 - New type */
DEBUG((BPGRP, FOLVL, "SUBFOR: Found operator."));
if (SubFor(n->lhs,bFirstLoop) ||
SubFor(n->rhs,bFirstLoop))
return(FAILURE);
for (j=0, io=n->rio; j < 10 && io; j++, io=io->nxt) {
// can't pass freed io->fname
DEBUG((BPGRP, FOLVL, "SUBFOR: s = %lx", &io->fname));
if (SFWork(n, &io->fname, j,bFirstLoop))
return(FAILURE);
DEBUG((BPGRP, FOLVL, "SUBFOR: *s = `%ws' &*s = %lx", io->fname, &io->fname));
}
return(SUCCESS);
/* M017 ends */
case FORTYP:
DEBUG((BPGRP, FOLVL, "SUBFOR: Found FOR."));
if (SFWork(n, &((struct fornode *) n)->arglist, 0,bFirstLoop))
return(FAILURE);
return(SubFor(((struct fornode *)n)->body,bFirstLoop));
case IFTYP:
DEBUG((BPGRP, FOLVL, "SUBFOR: Found IF."));
if (SubFor((struct node *)((struct ifnode *) n)->cond,bFirstLoop) ||
SubFor((struct node *)((struct ifnode *) n)->ifbody,bFirstLoop))
return(FAILURE);
return(SubFor(((struct ifnode *)n)->elsebody,bFirstLoop));
case NOTTYP:
DEBUG((BPGRP, FOLVL, "SUBFOR: Found NOT."));
return(SubFor((struct node *)((struct cmdnode *)n)->argptr,bFirstLoop));
case REMTYP: /* M009 - Rem now separate type */
case CMDTYP:
case CMDVERTYP:
case ERRTYP:
case DEFTYP:
case EXSTYP:
case STRTYP:
case CMPTYP:
DEBUG((BPGRP, FOLVL, "SUBFOR: Found command."));
if (SFWork(n, &((struct cmdnode *)n)->cmdline, 0,bFirstLoop) ||
SFWork(n, &((struct cmdnode *)n)->argptr, 1,bFirstLoop))
return(FAILURE);
for (j=2, io=n->rio; j < 12 && io; j++, io=io->nxt) {
// can't pass freed io->fname
DEBUG((BPGRP, FOLVL, "SUBFOR: s = %lx ", &io->fname) );
if (SFWork(n, &io->fname, j,bFirstLoop))
return(FAILURE);
DEBUG((BPGRP, FOLVL, "SUBFOR: *s = `%ws' &*s = %lx", io->fname, &io->fname));
}
/* M017 ends */
return(SUCCESS);
}
// If we get here we have an invalid node type. All case entries should
// return themselfs.
DEBUG((BPGRP, FOLVL, "SUBFOR: Invalid Node type"));
return(0);
}
/*** SFWork - does batch file variable substitutions
*
* Purpose:
* Make FOR variable substitutions in a single string. If a FOR loop
* substitution is being made, a pointer to the original string is
* saved so that it can be used for subsequent iterations.
*
* SFWork(struct node *n, TCHAR **src, int index)
*
* Args:
* n - parse tree node containing the string being substituted
* src - the string being examined
* index - index in save structure
* bFirstLoop - TRUE if first time through loop
*
* Returns:
* SUCCESS if substitutions could be made.
* FAILURE if the new string is too long.
*
* Notes:
*
*/
SFWork(n, src, index, bFirstLoop)
struct node *n;
TCHAR **src;
int index;
BOOL bFirstLoop;
{
TCHAR *dest; /* Destination string pointer */
TCHAR *srcstr, /* Source string pointer */
*srcpy, /* Copy of srcstr */
*t, /* Temp pointer */
c; /* Current character being copied */
int dlen; /* Length of dest string */
int sslen, /* Length of substr */
i; /* Work variable */
DEBUG((BPGRP, FOLVL, "SFW: Entered."));
if (*src == NULL) {
DEBUG((BPGRP, FOLVL, "SFW: Passed null ptr, returning now."));
return(SUCCESS);
}
/* If this string has been previously substituted, get the original string.
* Else, "*src" is the original.
*/
if (n->save.saveptrs[index]) {
srcpy = n->save.saveptrs[index];
DEBUG((BPGRP, FOLVL, "SFW: Src is saved string `%ws'",srcpy));
} else {
if (!bFirstLoop) {
// arg got created. get rid of it.
*src = NULL;
return(SUCCESS);
}
srcpy = *src;
DEBUG((BPGRP, FOLVL, "SFW: Src is passed string `%ws'",srcpy));
}
srcstr = srcpy;
if (!(dest = mkstr((MAXTOKLEN+1)*sizeof(TCHAR))))
return(FAILURE);
DEBUG((BPGRP, FOLVL, "SFW: dest = %lx", dest));
for (dlen = 0; (c = *srcstr++) && dlen <= MAXTOKLEN; ) {
//
// See if we have a percent character indicating a variable
// reference. If not, continue scanning.
//
if ( (c != PERCENT) || ( !(*srcstr)) || *srcstr == PERCENT) { /* @@4 */
DEBUG((BPGRP, FOLVL, " SFW: No PERCENT adding `%c'", c));
*dest++ = c;
dlen++;
continue;
}
//
// Found a percent character which might represent a for loop
// variable reference.
//
// If extensions are enabled then use the new substitution routine
// that supports path manipulation, etc. If it succeeds, accept
// its substitution.
//
if (fEnableExtensions && (t = MSCmdVar(NULL, srcstr, &sslen, Fvars, Fsubs))) {
srcstr += sslen;
sslen = mystrlen(t); /* Calc length */
if (dlen+sslen > MAXTOKLEN) /* Too long? */
return(FAILURE); /* ...yes, quit */
mystrcpy(dest, t);
dlen += sslen;
dest += sslen;
continue;
}
//
// Either extensions are disabled or new code could not
// resolve the variable references, so let the old code
// do it.
//
c = *srcstr++;
DEBUG((BPGRP, FOLVL, " SFW: Got PERCENT next is `%c'", c));
DEBUG((BPGRP, FOLVL, " SFW: Fvars are `%ws' @ %lx", Fvars, Fvars));
if (t = mystrrchr(Fvars,c)) { /* @@4 */ /* If c is var */
i = (int)(t - Fvars); /* ...make index */
DEBUG((BPGRP, FOLVL, " SFW: Found @ %lx", t));
DEBUG((BPGRP, FOLVL, " SFW: Index is %d", i));
DEBUG((BPGRP, FOLVL, " SFW: Substitute is `%ws'", Fsubs[i]));
sslen = mystrlen(Fsubs[i]); /* Calc length */
if (dlen+sslen > MAXTOKLEN) /* Too long? */
return(FAILURE); /* ...yes, quit */
DEBUG((BPGRP, FOLVL, " SFW: Copying to dest."));
mystrcpy(dest, Fsubs[i]);
dlen += sslen;
dest += sslen;
DEBUG((BPGRP, FOLVL, "SFW: Forsub, dest = `%ws'", dest-dlen));
} else {
DEBUG((BPGRP, FOLVL, " SFW: Not a var adding PERCENT and `%c'",c));
*dest++ = PERCENT;
*dest++ = c;
dlen += 2;
}
}
DEBUG((BPGRP, FOLVL, "SFW: Done, dlen = %d dest = `%ws'", dlen, dest-dlen));
if (dlen > MAXTOKLEN) {
DEBUG((BPGRP, FOLVL, "SFW: Error, too long."));
return(FAILURE);
}
DEBUG((BPGRP, FOLVL, "SFW: Saving FOR string."));
if (bFirstLoop) {
n->save.saveptrs[index] = srcpy;
}
if (!(*src = (TCHAR*)resize(dest-dlen, (dlen+1)*sizeof(TCHAR*)))) /* Free unused spc */
return(FAILURE);
DEBUG((BPGRP, FOLVL, "SFW: After resize *src = `%ws'", *src));
return(SUCCESS);
}
/*** ForFree - controls memory freeing during For loop execution
*
* Purpose:
* To free up space used during the execution of a for loop body as
* explained in the note in the comments for eFor(). If datacount
* is 0, this is the first time ForFree() has been called so DCount
* is used to get the number of elements on the data stack that must
* stay there for the corect execution of the loop. If datacount is
* not 0, it is the number discussed above. In this case, this number
* is passed to FreeStack().
*
* int ForFree(int datacount)
*
* Args:
* datacount - see above
*
* Returns:
* Datacount
*
*/
int ForFree(datacount)
int datacount;
{
if (datacount)
FreeStack(datacount);
else
datacount = DCount;
return(datacount);
}
/*** eGoto - executes a Goto statement
*
* Purpose:
* Find the label associated with the goto command and set the file
* position field in the current batch job structure to the position
* right after the label. After the label is found, set the GotoFlag.
* This tells function eFor() to stop executing a for loop and it
* tells Dispatch() that no more commands are to be executed until
* the flag is reset. This way, if the goto command is buried inside
* of any kind of compound statement, Command will be able to work its
* way out of the statement and reset I/O redirection before continuing
* with the statement after the label which was found.
*
* If the label isn't found, an error message is printed and the
* current batch job is terminated by popping its structure of the
* stack.
*
* If no batch job is in progress, this command is a nop.
*
* int eGoto(struct cmdnode *n)
*
* Args:
* n - parse tree node containing the goto command
*
* Returns:
* SUCCESS if the label is found.
* FAILURE otherwise.
*
* Notes:
* M030 - This function has been completely rewritten for speed-up
* of GOTO label searches. Now uses complete 257 byte temporary
* buffer.
* M031 - Function altered to speed up GOTO's. Optimized for
* forward searches and buffer increased to 512 bytes.
*
*/
//
// BUFFERLENGTH is the amount we read from the batch file each
// time we fill the internal buffer
//
#define BUFFERLENGTH 512
int eGoto(n)
struct cmdnode *n;
{
struct batdata *bdat;
unsigned cnt; /* Count of bytes read from file */
TCHAR s[128], /* Ptr to search label */
t[128], /* Ptr to found label */
*p1, /* Place keeper ptr 1 */
*p2, /* Place keeper ptr 2 */
*p3; /* Place keeper ptr 3 */
CRTHANDLE fh; /* Batch file handle */
int frstpass = TRUE, /* First time through indicator */
gotoretcode = SUCCESS; /* Just what it says */
long Backup, /* Rewind count for seek */
savepos; /* Save location for file pos */
DWORD filesize;
DEBUG((BPGRP, OTLVL, "GOTO: CurrentBatchFile = %lx", CurrentBatchFile));
if (!(bdat = CurrentBatchFile))
return(FAILURE);
//
// If no target of goto is present, then treat it like a label-not-found
//
if ( n->argptr == NULL) {
EndAllLocals(bdat);
CurrentBatchFile = bdat->backptr;
PutStdErr(MSG_NO_BAT_LABEL, NOARGS);
DEBUG((BPGRP, OTLVL, "GOTO: No label to goto, returning FAILURE, CurrentBatchFile = %lx", CurrentBatchFile));
return FAILURE;
}
ParseLabel( n->argptr, s, sizeof( s ) / sizeof( s[0] ), TRUE ); /* TRUE indicates source label */
savepos = bdat->filepos;
if ((fh = OpenPosBat( bdat )) == BADHANDLE)
return(FAILURE); /* Err if can't open */
DEBUG((BPGRP, OTLVL, "GOTO: label = %ws", s));
DEBUG((BPGRP, OTLVL, "GOTO: fh = %d", fh));
filesize = GetFileSize(CRTTONT(fh), NULL);
//
// If extensions are enabled, see if they are using the command script
// equivalent of return, which is GOTO :EOF. If so, set the current
// position to the end of file and fall through to the normal end of
// command script logic.
//
p2 = EatWS(n->argptr,NULL);
if (fEnableExtensions &&
!_tcsnicmp( p2, GotoEofStr, 4 ) &&
(!p2[4] || _istspace( p2[4] ))
) {
bdat->filepos = filesize;
GotoFlag = TRUE;
} else
for (;;) {
CheckCtrlC();
if (
//
// If we've read beyond where we started in the file and it's the second pass
//
((bdat->filepos = SetFilePointer( CRTTONT( fh ), 0, NULL, FILE_CURRENT )) >= savepos
&& !frstpass)
//
// or if we couldn't read from the batch file
//
|| ReadBufFromInput( CRTTONT( fh ), TmpBuf, BUFFERLENGTH, (LPDWORD)&cnt ) == 0
//
// or if there was nothing to read in the batch file (i.e., EOF)
//
|| cnt == 0
//
// or we detected EOF some other way
//
|| cnt == EOF
//
// or if we read a NULL line ???
//
|| TmpBuf[0] == NULLC
//
// or if the label to go to is empty
//
|| s[0] == NULLC) {
//
// If we are at EOF for the first time, then seek back to the beginning of the
// CMD file and continue scanning
//
if (cnt == 0 && frstpass) {
SetFilePointer( CRTTONT( fh ), 0L, NULL, FILE_BEGIN );
frstpass = FALSE;
continue;
}
//
// Terminate this batch file
//
EndAllLocals(bdat);
CurrentBatchFile = bdat->backptr;
PutStdErr(MSG_MISSING_BAT_LABEL, ONEARG, s);
DEBUG((BPGRP, OTLVL, "GOTO: Returning FAILURE, CurrentBatchFile = %lx", CurrentBatchFile));
gotoretcode = FAILURE;
break;
}
//
// Make sure input line is NUL terminated
//
TmpBuf[cnt] = NULLC;
DEBUG((BPGRP, OTLVL, "GOTO: Got %d bytes @ %lx", cnt, TmpBuf));
//
// If there's no :, then we just skip off to the next block of input
//
if (!(p1 = mystrchr( TmpBuf, COLON )))
continue;
DEBUG((BPGRP, OTLVL, "GOTO: Seeking through the buffer"));
//
// Walk through the input buffer looking for end-of-lines and
// testing to see if there's a label next
//
do {
DEBUG((BPGRP, OTLVL, "GOTO: Found COLON @ %lx.",p1));
DEBUG((BPGRP, OTLVL, "GOTO: Backing up to NLN."));
//
// Back up to the position of the previous EOL or beginning
// of the buffer
//
p2 = p1++;
while (*p2 != NLN && p2 != &TmpBuf[0]) {
--p2;
}
DEBUG((BPGRP, OTLVL, "GOTO: Found NLN @ %lx.",p1));
DEBUG((BPGRP, OTLVL, "GOTO: Trashing white space."));
if (*p2 != COLON)
++p2;
p3 = EatWS(p2,NULL); /* Fwd to 1st non-whtspc */
DEBUG((BPGRP,OTLVL,"GOTO: Found '%c' @ %lx.",*p2,p2));
if (*p3 == COLON) {
DEBUG((BPGRP, OTLVL, "GOTO: Possible label."));
//
// Scan forward for the end of the current line
//
p1 = mystrchr( p2, NLN );
//
// If we don't have a newline and we haven't read up to EOF, then we need to
// back up to the beginning of the line in the file and attempt to read it in
// in one entire block. Of course, there's a problem if the line is longer
// than the buffer. In this case, we simply treat the longer chars as being
// in the next line. Tough.
//
if (p1 == NULL
&& SetFilePointer( CRTTONT( fh ), 0, NULL, FILE_CURRENT ) != filesize
&& cnt != BUFFERLENGTH ) {
DEBUG((BPGRP, OTLVL, "GOTO: No NLN!"));
Backup = (long)(cnt - (p2 - &TmpBuf[0]));
#if defined(FE_SB) && defined(UNICODE) // eGoto()
if (IsDBCSCodePage()) {
// We should decrement file pointer in MBCS byte count.
// Because the file is described by MBCS string.
Backup = WideCharToMultiByte( CurrentCP, 0, TmpBuf, Backup, NULL, 0, NULL, NULL);
}
#endif // defined(FE_SB) && defined(UNICODE)
SetFilePointer(CRTTONT(fh), -Backup, NULL, FILE_CURRENT);
DEBUG((BPGRP, OTLVL, "GOTO: Rewound %ld", Backup));
break; /* Read more */
}
ParseLabel( p3, t, sizeof( t ) / sizeof( t[0] ), FALSE ); /* FALSE = target */
DEBUG((BPGRP,OTLVL,"GOTO: Found label %ws at %lx.",t,p1));
if (_tcsicmp(s, t) == 0) {
DEBUG((BPGRP,OTLVL,"GOTO: A match!"));
GotoFlag = (n->flag & CMDNODE_FLAG_GOTO) != 0;
DEBUG((BPGRP,OTLVL,"GOTO: NLN at %lx",p1));
DEBUG((BPGRP,OTLVL,"GOTO: File pos is %04lx",bdat->filepos));
DEBUG((BPGRP,OTLVL,"GOTO: Adding %lx - %lx = %lx bytes",p1+1,&TmpBuf[0],(p1+1)-&TmpBuf[0]));
#if defined(FE_SB) // eGoto()
// We should increment file pointer in MBCS byte count.
// Because the file is described by MBCS string.
if ( !p1 ) {
#ifdef UNICODE
if (IsDBCSCodePage()) {
long cbMbcs;
cbMbcs = WideCharToMultiByte( CurrentCP, 0, TmpBuf, cnt,
NULL, 0, NULL, NULL);
bdat->filepos += cbMbcs;
} else
bdat->filepos += (long)cnt; /* @@4 */
#else
bdat->filepos += (long)cnt; /* @@4 */
#endif
} else {
#ifdef UNICODE
if (IsDBCSCodePage()) {
long cbMbcs;
cbMbcs = WideCharToMultiByte(CurrentCP,0,TmpBuf,(int)(++p1 - &TmpBuf[0]),
NULL,0,NULL,NULL);
bdat->filepos += cbMbcs;
} else
bdat->filepos += (long)(++p1 - &TmpBuf[0]);
#else
bdat->filepos += (long)(++p1 - &TmpBuf[0]);
#endif
}
#else
if ( !p1 ) { /* @@4 */
bdat->filepos += (long)cnt; /* @@4 */
} else { /* @@4 */
bdat->filepos += (long)(++p1 - &TmpBuf[0]);
}
#endif // defined(FE_SB)
DEBUG((BPGRP,OTLVL,"GOTO: File pos changed to %04lx",bdat->filepos));
break;
}
}
DEBUG((BPGRP,OTLVL,"GOTO: Next do loop iteration."));
} while (p1 = mystrchr(p1,COLON));
DEBUG((BPGRP,OTLVL,"GOTO: Out of do loop GotoFlag = %d.",GotoFlag));
if (GotoFlag == TRUE)
break;
DEBUG((BPGRP,OTLVL,"GOTO: Next for loop iteration."));
}
DEBUG((BPGRP,OTLVL,"GOTO: Out of for loop retcode = %d.",gotoretcode));
Cclose(fh); /* M023 */
return(gotoretcode);
}
/*** eIf - controls the execution of an If statement
*
* Purpose:
* Execute the IF conditional. If the conditional function returns a
* nonzero value, execute the body of the if statement. Otherwise,
* execute the body of the else.
*
* int eIf(struct ifnode *n)
*
* Args:
* n - the node containing the if statement
*
* Returns:
* The retcode from which ever body (ifbody or elsebody) is executed.
*
*/
int eIf(struct ifnode *pIfNode)
{
int i;
struct cmdnode *n;
BOOLEAN bNot;
DEBUG((BPGRP, IFLVL, "IF: cond type = %d", pIfNode->cond->type));
/* The following checks the syntax of an errorlevel arg
to ensure that only numeric digits are specified.
*/
n = pIfNode->cond;
if (n->type == NOTTYP) {
bNot = TRUE;
n = (struct cmdnode *)n->argptr;
} else {
bNot = FALSE;
}
if (n->type == ERRTYP || n->type == CMDVERTYP) {
for (i = 0; n->argptr[i] != 0; i++) {
if (i == 0 && n->type == ERRTYP && n->argptr[i] == MINUS) {
continue;
}
if (!_istdigit(n->argptr[i])) {
PutStdErr(MSG_SYNERR_GENL, ONEARG, n->argptr);
return(FAILURE);
}
}
}
if (bNot ^ (BOOLEAN)((*GetFuncPtr(n->type))(n) != 0)) {
DEBUG((BPGRP, IFLVL, "IF: Executing IF body."));
return(Dispatch(RIO_OTHER,pIfNode->ifbody)); /* M000 */
} else {
DEBUG((BPGRP, IFLVL, "IF: Executing ELSE body."));
return(Dispatch(RIO_OTHER,pIfNode->elsebody)); /* M000 */
}
return(SUCCESS);
}
/*** eErrorLevel - executes an errrorlevel If conditional
*
* Purpose:
* If LastRetCode >= the errorlevel in the node, return 1. If not,
* return 0.
*
* int eErrorLevel(struct cmdnode *n)
*
* Args:
* n - parse tree node containing the errorlevel command
*
* Returns:
* See above.
*
*/
int eErrorLevel(n)
struct cmdnode *n;
{
DEBUG((BPGRP, IFLVL, "ERRORLEVEL: argptr = `%ws' LRC = %d", n->argptr, LastRetCode));
return(_tcstol(n->argptr, NULL, 10) <= LastRetCode);
}
/*** eCmdExtVer - executes an CMDEXTVERSION If conditional
*
* Purpose:
* If CMDEXTVERSION >= the value in the node, return 1. If not,
* return 0. This routine is never called unless command extensions
* are enabled.
*
* int eCmdExtVer(struct cmdnode *n)
*
* Args:
* n - parse tree node containing the CMDEXTVERSION command
*
* Returns:
* See above.
*
*/
int eCmdExtVer(n)
struct cmdnode *n;
{
DEBUG((BPGRP, IFLVL, "CMDEXTVERSION: argptr = `%ws' VER = %d", n->argptr, CMDEXTVERSION));
return(_tcstol(n->argptr, NULL, 10) <= CMDEXTVERSION);
}
/*** eDefined - execute the DEFINED conditional of an if statement
*
* Purpose:
* Return 1 if the environment variable in node n exists. Otherwise return 0.
* This routine is never called unless command extensions are enabled.
*
* int eDefined(struct cmdnode *n)
*
* Args:
* n - parse tree node containing the exist command
*
* Returns:
* See above.
*
*/
int eDefined(n)
struct cmdnode *n;
{
return(GetEnvVar(n->argptr)!= NULL);
}
/*** eExist - execute the exist conditional of an if statement
*
* Purpose:
* Return 1 if the file in node n exists. Otherwise return 0.
*
* int eExist(struct cmdnode *n)
*
* Args:
* n - parse tree node containing the exist command
*
* Returns:
* See above.
*
*/
int eExist(n)
struct cmdnode *n;
{
return(exists(n->argptr));
}
/*** eNot - execute the not condition of an if statement
*
* Purpose:
* Return the negated result of the if conditional pointed to by
* n->argptr.
*
* int eNot(struct cmdnode *n)
*
* Args:
* n - parse tree node containing the not command
*
* Returns:
* See above.
*
*/
int eNot(n)
struct cmdnode *n;
{
UNREFERENCED_PARAMETER( n );
#if DBG
cmd_printf( TEXT("CMD: should never get here\n") );
DebugBreak();
#endif
return 0;
}
/*** eStrCmp - execute an if statement string comparison
*
* Purpose:
* Return a nonzero value if the 2 strings in the node are equal.
* Otherwise return 0.
*
* int eStrCmp(struct cmdnode *n)
*
* Args:
* n - the parse tree node containing the string comparison command
*
* Returns:
* See above.
*
*/
int eStrCmp(n)
struct cmdnode *n;
{
DEBUG((BPGRP, IFLVL, "STRCMP: returning %d", !_tcscmp(n->cmdline, n->argptr)));
//
// If the parse node says to ignore case, do a case insensitive compare
// otherwise case sensitive. The ignore case will never be set unless
// command extensions are enabled.
//
if (n->flag & CMDNODE_FLAG_IF_IGNCASE)
return(!lstrcmpi(n->cmdline, n->argptr));
else
return(!lstrcmp(n->cmdline, n->argptr));
}
/*** eGenCmp - execute an if statement comparison - general case
*
* Purpose:
* Return a nonzero value if comparison condition is met.
* Otherwise return 0. This routine is never called unless
* command extensions are enabled.
*
* int eStrCmp(struct cmdnode *n)
*
* Args:
* n - the parse tree node containing the string comparison command
*
* Returns:
* See above.
*
*/
int eGenCmp(n)
struct cmdnode *n;
{
TCHAR *s1, *s2;
LONG n1, n2, iCompare;
n1 = _tcstol(n->cmdline, &s1, 0);
n2 = _tcstol(n->argptr, &s2, 0);
if (*s1 == NULLC && *s2 == NULLC)
iCompare = n1 - n2;
else
if (n->flag & CMDNODE_FLAG_IF_IGNCASE)
iCompare = lstrcmpi(n->cmdline, n->argptr);
else
iCompare = lstrcmp(n->cmdline, n->argptr);
switch (n->cmdarg) {
case CMDNODE_ARG_IF_EQU:
return iCompare == 0;
case CMDNODE_ARG_IF_NEQ:
return iCompare != 0;
case CMDNODE_ARG_IF_LSS:
return iCompare < 0;
case CMDNODE_ARG_IF_LEQ:
return iCompare <= 0;
case CMDNODE_ARG_IF_GTR:
return iCompare > 0;
case CMDNODE_ARG_IF_GEQ:
return iCompare >= 0;
}
return 0;
}
/*** ePause - execute the Pause command
*
* Purpose:
* Print a message and pause until a character is typed.
*
* int ePause(struct cmdnode *n)
*
* Args:
* n - parse tree node containing the pause command
*
* Returns:
* SUCCESS always.
*
* Notes:
* M025 - Altered to use DOSREAD for pause response and to use
* new function SetKMode to insure that if STDIN is KBD, it will
* will be in raw mode when DOSREAD accesses it.
* M041 - Changed to use single byte var for input buffer.
* - Changed to do direct KB read if STDIN == KBD.
*
*/
int ePause(n)
struct cmdnode *n;
{
ULONG cnt; // Count of response bytes
TCHAR c; // Retrieval buffer
UNREFERENCED_PARAMETER( n );
DEBUG((BPGRP, OTLVL, "PAUSE"));
PutStdOut(MSG_STRIKE_ANY_KEY, NOARGS);
if (FileIsDevice(STDIN) && (flgwd & 1)) {
FlushConsoleInputBuffer( GetStdHandle(STD_INPUT_HANDLE) );
c = (TCHAR)_getch();
if (c == 0x3) {
SetCtrlC();
}
} else {
ReadBufFromInput(
GetStdHandle(STD_INPUT_HANDLE),
(TCHAR*)&c, 1, (LPDWORD)&cnt);
}
cmd_printf(CrLf);
return(SUCCESS);
}
/*** eShift - execute the Shift command
*
* Purpose:
* If a batch job is being executed, shift the batch job's vars one to the
* left. The value for %0 is never shifted. The value for %1 is lost.
* If there are args that have not been assigned to a variable, the next
* one is assigned to %9. Otherwise, %9's value is NULLed.
*
* If no batch job is in progress, just return.
*
* int eShift(struct cmdnode *n)
*
* Returns:
* SUCCESS always.
*
* Notes:
* As of Modification number M004, the value of %0 is now included in
* in the shift command.
*/
int eShift(n)
struct cmdnode *n;
{
struct batdata *bdat;
TCHAR *s;
int iStart;
int i;
DEBUG((BPGRP, OTLVL, "SHIFT: CurrentBatchFile = %lx", CurrentBatchFile));
if (CurrentBatchFile) {
bdat = CurrentBatchFile;
//
// If extensions are enabled, look for /n switch that specifies
// the starting index of the shift. Zero is the default starting
// index.
//
iStart = 0;
if (fEnableExtensions && n->argptr) {
s = EatWS( n->argptr, NULL );
if (s[0] == SWITCHAR && (s[1] >= L'0' && s[1] < L'9')) {
iStart = s[1] - L'0';
} else if (_tcslen(s)) {
PutStdErr(MSG_SHIFT_BAD_ARG, NOARGS);
LastRetCode = FAILURE;
return FAILURE;
}
}
for (i = iStart; i < 9; i++) {
bdat->aptrs[i] = bdat->aptrs[i+1];
bdat->alens[i] = bdat->alens[i+1];
DEBUG((BPGRP, OTLVL, "SHIFT: #%d addr = %lx len = %d", i, bdat->aptrs[i], bdat->alens[i]));
}
if ((bdat->args) && (*bdat->args)) {
bdat->aptrs[9] = bdat->args;
bdat->alens[9] = i = mystrlen(bdat->args);
bdat->args += i+1;
DEBUG((BPGRP, OTLVL, "SHIFT: #9 %lx len = %d args = %ws", bdat->aptrs[9], bdat->alens[9], bdat->args));
} else {
bdat->aptrs[9] = NULL;
bdat->alens[9] = 0;
DEBUG((BPGRP, OTLVL, "SHIFT: #9 was NULLed."));
}
}
return(SUCCESS);
}
/*** eSetlocal - Begin Local treatment of environment commands
*
* Purpose:
* To prevent the export of environment alterations to COMMAND's
* current environment by saving copies of the current directory
* and environment in use at the time.
*
* int eSetlocal(struct cmdnode *n)
*
* Args:
* n - the parse tree node containing the SETLOCAL command
*
* Returns:
* Always returns SUCCESS.
*
* Notes:
* - All directory and environment alterations occuring after the
* execution of this command will affect only the copies made and
* hence will be local to this batch file (and child processes
* invoked by this batch file) until a subsequent ENDLOCAL command
* is executed.
* - The data stack level, referenced by CurrentBatchFile->stacksize, does not
* include the memory malloc'd for saving the directory & environment.
* As a result, the next call to Parser() would free up these items.
* To prevent this, the data stack pointer in the current batch data
* structure, is set to a level beyond these two items; including also
* some memory malloc'd in functions between the last call to Parser()
* and the current execution of eSetlocal(). This memory will only be
* freed when Parser() is called following termination of the current
* batch file. To attempt to save the current stack level and restore
* it in eEndlocal() works only if both commands occur in the same
* file. If eEndlocal() comes in a nested file, the resulting freeing
* of memory by Parser() would also eliminate even the batch data
* structures occuring between the two.
*
*/
int eSetlocal(n)
struct cmdnode *n;
{
struct batsaveddata *p;
TCHAR *tas; /* Tokenized argument list */
if (CurrentBatchFile) {
if (CurrentBatchFile->SavedEnvironmentCount < CMD_MAX_SAVED_ENV) { // Check also CurrentBatchFile
DEBUG((BPGRP, OTLVL, "SLOC: Performing localizing"));
p = HeapAlloc( GetProcessHeap( ), HEAP_ZERO_MEMORY, sizeof( *p ));
if (!p)
return FAILURE;
p->dircpy = HeapAlloc( GetProcessHeap( ),
HEAP_ZERO_MEMORY,
mystrlen( CurDrvDir )*sizeof( TCHAR )+sizeof( TCHAR ));
if (!p->dircpy)
return FAILURE;
else
mystrcpy(p->dircpy, CurDrvDir);
p->envcpy = CopyEnv();
if (!p->envcpy)
return FAILURE;
//
// Save this in case it is modified, so it can be
// restored when the matching ENDLOCAL is executed.
//
p->fEnableExtensions = fEnableExtensions;
p->fDelayedExpansion = fDelayedExpansion;
CurrentBatchFile->saveddata[CurrentBatchFile->SavedEnvironmentCount] = p;
CurrentBatchFile->SavedEnvironmentCount += 1;
if (CurrentBatchFile->stacksize < (CurrentBatchFile->stackmin = DCount)) {
CurrentBatchFile->stacksize = DCount;
}
//
// If there is addional text on the command line, see
// if it matches various keywords that enable or disable
// features inside scripts.
//
// We do this regardless
// of where extensions are currently enabled, so we can
// use this mechanism to temporarily turn on/off extensions
// from inside of a command script as needed. The original
// CMD.EXE ignored any extra text on the SETLOCAL command
// line, did not declare an error and did not set ERRORLEVEL
// Now it looks for the extra text and declares and error
// if it does not match one of the acceptable keywords and
// sets ERRORLEVEL to 1 if it does not.
//
// Very minor incompatibility with old command scripts that
// that should not effect anybody.
//
tas = TokStr(n->argptr, NULL, TS_NOFLAGS);
LastRetCode = SUCCESS;
while (*tas != NULLC) {
if (!_tcsicmp( tas, TEXT("ENABLEEXTENSIONS"))) {
fEnableExtensions = TRUE;
} else if (!_tcsicmp( tas, TEXT("DISABLEEXTENSIONS"))) {
fEnableExtensions = FALSE;
} else if (!_tcsicmp( tas, TEXT( "ENABLEDELAYEDEXPANSION" ))) {
fDelayedExpansion = TRUE;
} else if (!_tcsicmp( tas, TEXT( "DISABLEDELAYEDEXPANSION" ))) {
fDelayedExpansion = FALSE;
} else if (*tas != NULLC) {
PutStdErr(MSG_SETLOCAL_BAD_ARG, NOARGS);
LastRetCode = FAILURE;
return FAILURE;
}
tas += mystrlen( tas ) + 1;
}
} else {
PutStdErr(MSG_MAX_SETLOCAL,NOARGS);
return FAILURE;
}
}
DEBUG((BPGRP, OTLVL, "SLOC: Exiting"));
return(SUCCESS);
}
/*** eEndlocal - End Local treatment of environment commands
*
* Purpose:
* To reestablish the export of environment alterations to COMMAND's
* current environment. Once this command is encountered, the current
* directory and the current environment in use at the time of the
* initial SETLOCAL command will be restored from their copies.
*
* int eEndlocal(struct cmdnode *n)
*
* Args:
* n - the parse tree node containing the ENDLOCAL command
*
* Returns:
* Always returns SUCCESS.
*
* Notes:
* Issuance of an ENDLOCAL command without a previous SETLOCAL command
* is bad programming practice but not considered an error.
*
*/
int eEndlocal(n)
struct cmdnode *n;
{
struct batdata *bdat = CurrentBatchFile;
UNREFERENCED_PARAMETER( n );
ElclWork( bdat );
return(SUCCESS);
}
void EndAllLocals( struct batdata *bdat )
{
//
// If a restricted token was created to run this batch file then close it.
// Also, revert to the process token. The matching impersonate was done in
// SetBat.
//
if (bdat->hRestrictedToken != NULL) {
RevertToSelf();
CloseHandle(bdat->hRestrictedToken);
bdat->hRestrictedToken = NULL;
}
while (bdat->SavedEnvironmentCount != 0) {
ElclWork( bdat );
}
}
/*** ElclWork - Restore copied directory and environment
*
* Purpose:
* If the current batch data structure contains valid pointers to
* copies of the current directory and environment, restore them.
*
* int ElclWork(struct batdata *bdat)
*
* Args:
* bdat - the batch data structure containing copied dir/env pointers
*
* Returns:
* Always returns SUCCESS.
*
* Notes:
* The level of stacked data, ie. CurrentBatchFile->stacksize, cannot be restored
* to its pre-SETLOCAL level in case this command is occuring in a
* later nested batch file. To do so would free the memory containing
* its own batch data structure. Only when the current batch file
* terminates and is popped off the stack, will Parser() free up the
* memory containing the copies. Issuance of an ENDLOCAL command
* without a previous SETLOCAL command is bad programming practice
* but not considered an error.
*
*/
void ElclWork( struct batdata *bdat )
{
TCHAR c;
struct batsaveddata *p;
if (bdat == NULL) {
return;
}
if (bdat->SavedEnvironmentCount == 0) {
return;
}
bdat->SavedEnvironmentCount--;
p = bdat->saveddata[bdat->SavedEnvironmentCount];
bdat->saveddata[bdat->SavedEnvironmentCount] = NULL;
if (p == NULL) {
return;
}
c = _toupper( *p->dircpy );
if (CurDrvDir[0] != c) {
ChangeDrive( c - 0x20 );
}
//
// Restore the state saved in the setlocal
//
ChangeDir( p->dircpy);
HeapFree( GetProcessHeap( ), 0, p->dircpy );
ResetEnv( p->envcpy );
FreeEnv( p->envcpy );
fEnableExtensions = p->fEnableExtensions;
fDelayedExpansion = p->fDelayedExpansion;
HeapFree( GetProcessHeap( ), 0, p );
}
/*** eCall - begin the execution of the Call command
*
* Purpose:
* This is Command's interface to the Call function. It just calls
* CallWork with its command node, and sets LastRetCode.
*
* int eCall(struct cmdnode *n)
*
* Args:
* n - the parse tree node containing the copy command
*
* Returns:
* Whatever CallWork() returns.
*
*/
int eCall(n)
struct cmdnode *n;
{
int CallWork();
return(LastRetCode = CallWork(n->argptr)); /* @@ */
}
/*** CallWork - Execute another batch file as a subroutine (M009 - New)
*
* Purpose:
* Parse the argument portion of the current node. If it is a batch
* file invocation, call BatProc() with the newly parsed node.
*
* int CallWork(TCHAR *fname)
*
* Args:
* fname - pointer to the batch file to be CALLed
*
* Returns:
* The process return code of the child batch file or
* SUCCESS if null node or
* FAILURE if PARSERROR or unable to exec as batch file.
*
* Notes:
* The CALLing of batch files is much the same as the proposed
* "new-style" batch file concept, except with regard to localizing
* environment and directory alterations.
*
*/
int ColonIsToken;
int CallWork(fname)
TCHAR *fname;
{
struct node *c; /* New node for CALL statement */
TCHAR *flptr; /* Ptr to file location */
int i; /* Work variable */
TCHAR *t1, *t2, /* M041 - Temp pointer */
*aptr; /* M041 - New arg pointer */
TCHAR *temp_parm; /* @@4a */
unsigned rc;
DEBUG((BPGRP,OTLVL,"CALL: entered"));
if (fname == NULL) {
return( FAILURE );
}
if (!(flptr = mkstr(MAX_PATH*sizeof(TCHAR)))) /* Filespec to run */
return(FAILURE);
/* Note that in reparsing the argument portion of the current statement
* we do not have to concern ourselves with redirection. It was already
* set up when the CALL statement was dispatch()'ed.
* M041 - We do, however, have to "re-escape" any escape characters
* before reparsing or they will disappear.
*/
aptr = fname; /* Initialize it */
if (t1 = mystrchr(fname, ESCHAR)) {
if (!(aptr = mkstr(((mystrlen(fname) * 2) + 1) * sizeof(TCHAR))))
return(FAILURE);
t2 = aptr;
t1 = fname;
while (*t1)
if ((*t2++ = *t1++) == ESCHAR)
*t2++ = ESCHAR;
*t2 = NULLC;
if (!(aptr = resize(aptr, (mystrlen(aptr) + 1)*sizeof(TCHAR))))
return(FAILURE);
}
i = DCount; /* Valid data ptr for parser */
DEBUG((BPGRP,OTLVL,"CALL: Parsing %ws",fname));
ColonIsToken = 1;
c=Parser(READSTRING, (INT_PTR)aptr, i);
ColonIsToken = 0;
if (c == (struct node *) PARSERROR) {
DEBUG((BPGRP,OTLVL,"CALL: Parse error, returning failure"));
/*@@5c */
if (!(temp_parm = mkstr(((mystrlen(aptr) * 2) + 1) * sizeof(TCHAR))))
return(FAILURE);
/*@@5a */mystrcpy(temp_parm, aptr);
_tcsupr(temp_parm);
/*@@5a */
/*@@5a */if ( (!_tcscmp(temp_parm, TEXT(" IF" ))) ||
/*@@5a */ (!_tcscmp(temp_parm, TEXT(" FOR" ))) )
/*@@5a */
{
/*@@5a */
PutStdErr( MSG_SYNERR_GENL, ONEARG, aptr ); /* @@4 */
/*@@5a */
}
return(FAILURE);
}
if (c == (struct node *) EOF) {
DEBUG((BPGRP,OTLVL,"CALL: Found EOF, returning success"));
return(SUCCESS);
}
DEBUG((BPGRP,OTLVL,"CALL: Parsed OK, looking for batch file"));
//
// If extensions are enable, check for the new form of the CALL
// statement:
//
// CALL :label args...
//
// which is basically a form of subroutine call within command scripts.
// If the target of the CALL begins with a COLON then do nothing
// here and let BatProc take care of it when it is called below.
//
// Otherwise, execute the old code, which will search for a command
// script file or executable.
//
if (fEnableExtensions && *((struct cmdnode *)c)->cmdline == COLON) {
//
// The new form is only valid inside of a command script, so
// declare an error if the user entered it from the command line
//
if (CurrentBatchFile == NULL) {
PutStdErr( MSG_CALL_LABEL_INVALID, NOARGS );
return(FAILURE);
}
} else
if ((mystrchr(((struct cmdnode *)c)->cmdline, STAR) || /* M035 */
mystrchr(((struct cmdnode *)c)->cmdline, QMARK) ||
(i = SearchForExecutable((struct cmdnode *)c, flptr)) != SFE_ISBAT)) {
rc = FindFixAndRun( (struct cmdnode *)c );
return(rc); /*@@5*/
}
DEBUG((BPGRP,OTLVL,"CALL: Found batch file"));
rc = BatProc((struct cmdnode *)c, flptr, BT_CALL);
/* @@6a If rc is zero, return LastRetCode because it might != 0 */
return(rc ? rc : LastRetCode);
}
/*** eBreak - begin the execution of the BREAK command
*
* Purpose:
* Does nothing as it is only here for compatibility. If extensions are
* enabled and running on Windows NT, then enters a hard coded breakpoint
* if this process is being debugged by a debugger.
*
* int eExtproc(struct cmdnode *n)
*
* Args:
* n - the parse tree node containing the copy command
*
* Returns:
* SUCCESS;
*
*/
int eBreak(struct cmdnode *n)
{
UNREFERENCED_PARAMETER( n );
#if !defined( WIN95_CMD ) && DBG
if (fEnableExtensions &&
lpIsDebuggerPresent != NULL && // Only true on NT
(*lpIsDebuggerPresent)()) {
DebugBreak();
}
#endif
return(SUCCESS);
}
BOOL
ReadBufFromFile(
HANDLE h,
TCHAR *pBuf,
int cch,
int *pcch)
{
int cb;
UCHAR *pch = AnsiBuf;
int cchNew;
DWORD fPos;
fPos = SetFilePointer(h, 0, NULL, FILE_CURRENT);
if (ReadFile(h, AnsiBuf, cch, pcch, NULL) == 0)
return 0;
if (*pcch == 0)
return 0;
/* check for lead character at end of line */
cb = cchNew = *pcch;
while (cb > 0) {
if ( (cb >=3 ) &&
( (*pch == '\n' && *(pch+1) == '\r') ||
(*pch == '\r' && *(pch+1) == '\n') ) ) {
*(pch+2) = '\000';
cchNew = (int)(pch - AnsiBuf) + 2;
SetFilePointer(h, fPos+cchNew, NULL, FILE_BEGIN);
break;
} else if (is_dbcsleadchar(*pch)) {
if (cb == 1) {
if (ReadFile(h, pch+1, 1, &cb, NULL) == 0 || cb == 0) {
*pcch = 0;
return 0;
}
cchNew++;
break;
}
cb -= 2;
pch += 2;
} else {
cb--;
pch++;
}
}
#ifdef UNICODE
cch = MultiByteToWideChar(CurrentCP, MB_PRECOMPOSED, AnsiBuf, cchNew, pBuf, cch);
#else
memmove(pBuf, AnsiBuf, cchNew);
cch = cchNew;
#endif
*pcch = cch;
return cch;
}
BOOL
ReadBufFromConsole(
HANDLE h,
TCHAR* pBuf,
int cch,
int *pcch)
{
CONSOLE_READCONSOLE_CONTROL InputControl;
BOOL ReadConsoleResult, bTouched, bPathCompletion, bHaveHScrollBar, bHaveVScrollBar;
PTCHAR PrevBuf;
DWORD cchPrevBuf;
DWORD cchRead;
CONSOLE_SCREEN_BUFFER_INFO csbi;
COORD InitialCursorPosition;
HANDLE hOut;
DWORD cchBuf;
ULONG i, iCompletionCh, iCR;
DWORD nLines, nCols;
//
// Original code just called ReadConsole with the passed parameters.
// Now, we attempt to call the new improved ReadConsole with an extra
// parameter to enable intermediate wakeups from the read to process
// a file completion control character. This new feature is only
// enabled if all of the following are true:
// Command extensions are enabled
// User has defined a command completion control character
// Standard Output Handle is a console output handle
//
// If any of the above are not true, do it the old way.
//
hOut = GetStdHandle( STD_OUTPUT_HANDLE );
if (hOut == INVALID_HANDLE_VALUE)
hOut = CRTTONT( STDOUT );
if (!fEnableExtensions
|| chCompletionCtrl >= SPACE
|| chPathCompletionCtrl >= SPACE
|| !GetConsoleScreenBufferInfo( hOut, &csbi ) ) {
ReadConsoleResult = ReadConsole(h, pBuf, cch, pcch, NULL);
#if defined(RICHARDW)
Mirror( ReadConsoleResult, pBuf, *pcch );
#endif
return ReadConsoleResult;
}
InitialCursorPosition = csbi.dwCursorPosition;
nLines = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
nCols = csbi.srWindow.Right - csbi.srWindow.Left + 1;
bHaveHScrollBar = ((SHORT)nCols != csbi.dwSize.X);
bHaveVScrollBar = ((SHORT)nLines != csbi.dwSize.Y);
//
// All conditions are met, so set up the extra parameter to
// ReadConsole to tell it what control character(s) are to
// cause the read to return with intermediate results.
//
InputControl.nLength = sizeof(CONSOLE_READCONSOLE_CONTROL);
InputControl.nInitialChars = 0;
InputControl.dwCtrlWakeupMask = (1 << chCompletionCtrl);
InputControl.dwCtrlWakeupMask |= (1 << chPathCompletionCtrl);
InputControl.dwControlKeyState = 0;
//
// We will now loop until the user type enter, processing any
// intermediate wakeups as file completion requests.
//
DoCompleteInitialize();
PrevBuf = NULL;
cchPrevBuf = 0;
while (TRUE) {
//
// Read a line of input from the console.
//
ReadConsoleResult = ReadConsole(h, pBuf, cch, pcch, &InputControl);
#if defined( RICHARDW )
Mirror( ReadConsoleResult, pBuf, *pcch );
#endif
cchRead = *pcch;
if (CtrlCSeen) {
ResetCtrlC( );
if (PrevBuf)
HeapFree(GetProcessHeap(), 0, PrevBuf);
PrevBuf = NULL;
}
//
// If the read failed for any reason, we are done.
//
if (!ReadConsoleResult)
break;
//
// Make sure the result buffer is null terminated. If the buffer
// contains a carriage return, then the use must have hit enter, so
// break out of the loop to return the command line to the caller.
//
bPathCompletion = FALSE;
iCR = iCompletionCh = 0xFFFFFFFF;
for (i=0; i<(ULONG)*pcch; i++) {
if (pBuf[i] == CR) {
iCR = i;
break;
} else
if (pBuf[i] == chCompletionCtrl) {
iCompletionCh = i;
break;
} else
if (pBuf[i] == chPathCompletionCtrl) {
iCompletionCh = i;
bPathCompletion = TRUE;
break;
}
}
if (iCR != 0xFFFFFFFF) {
break;
}
//
// Use did not hit enter, so they must have hit the file completion
// control character. Find where they did this and terminate the
// result buffer at that point. If not found, then assume they hit
// enter and break out of the loop to return what we have.
//
if (iCompletionCh == 0xFFFFFFFF) {
break;
}
//
// Found the file completion control character. Dont count it as read.
// Null terminate the buffer and see if the buffer contents before
// the completion character is the same as what we displayed last.
//
*pcch = iCompletionCh;
pBuf[iCompletionCh] = NULLC;
if (PrevBuf == NULL || _tcscmp(pBuf, PrevBuf))
bTouched = TRUE;
else
bTouched = FALSE;
//
// If we know we are processing a command that only takes directory
// names are arguments, force completion code to only match directory
// names.
//
if (!bPathCompletion && iCompletionCh > 2) {
if (!_tcsnicmp(pBuf, TEXT("cd "), 3)
|| !_tcsnicmp(pBuf, TEXT("rd "), 3)
|| !_tcsnicmp(pBuf, TEXT("md "), 3)
|| !_tcsnicmp(pBuf, TEXT("chdir "), 6)
|| !_tcsnicmp(pBuf, TEXT("rmdir "), 6)
|| !_tcsnicmp(pBuf, TEXT("mkdir "), 6)
|| !_tcsnicmp(pBuf, TEXT("pushd "), 6)
) {
bPathCompletion = TRUE;
}
}
//
// Call the file completion code with the input buffer, current length,
// whether the user had the shift key down or not (SHIFT means backwards)
// and whether or not the user modified the input buffer since the last
// time it was displayed. If the user did not modify what was last displayed
// then that tells the file completion code to display the next matching
// file name from the list as opposed to recalculating the list of matching
// files.
//
if ( DoComplete( pBuf,
iCompletionCh,
cch,
!(InputControl.dwControlKeyState & SHIFT_PRESSED),
bPathCompletion,
bTouched
)
) {
//
// Recompute the position of the start of the command line, since
// it might have scrolled around since when we first wrote it out.
//
if (GetConsoleScreenBufferInfo( hOut, &csbi )) {
//
// Walk backwards from the current cursor position to determine the
// proper initial position for screen blanking and for the next
// ReadConsole
//
// The row (Y) of the next input is based on the number of lines
// consumed by the prompt plus the length of what the user has input.
// There is no need to round at all since any remainder is simply
// how far on the screen we are.
//
InitialCursorPosition.Y =
(SHORT) (csbi.dwCursorPosition.Y
- (InitialCursorPosition.X + iCompletionCh) / csbi.dwSize.X);
}
//
// Completion found a new file name and put it in the buffer.
// Update the length of valid characters in the buffer, redisplay
// the buffer at the cursor position we started, so the user can
// see the file name found
//
cchBuf = _tcslen(pBuf);
SetConsoleCursorPosition( hOut, InitialCursorPosition );
FillConsoleOutputCharacter( hOut,
TEXT( ' ' ),
cchRead,
InitialCursorPosition,
&cchRead );
WriteConsole(hOut, pBuf, cchBuf, &cchBuf, NULL);
InputControl.nInitialChars = cchBuf;
} else {
//
// File completion had nothing to had, so just beep and redo the read.
//
MessageBeep( 0xFFFFFFFF );
InputControl.nInitialChars = _tcslen(pBuf);
}
//
// Done with file completion. Free any previous buffer copy and
// allocate a copy of the current input buffer so we will know if the
// user has changed it or not.
//
if (PrevBuf)
HeapFree(GetProcessHeap(), 0, PrevBuf);
cchPrevBuf = _tcslen(pBuf);
PrevBuf = HeapAlloc(GetProcessHeap(), 0, (cchPrevBuf+1) * sizeof(TCHAR));
if (PrevBuf == NULL) {
return FALSE;
}
_tcscpy(PrevBuf, pBuf);
}
//
// All done. Free any buffer copy and return the result of the read
// to the caller
//
if (PrevBuf)
HeapFree(GetProcessHeap(), 0, PrevBuf);
return ReadConsoleResult;
}
BOOL
ReadBufFromInput(
HANDLE h,
TCHAR *pBuf,
int cch,
int *pcch)
{
unsigned htype;
htype = GetFileType(h);
htype &= ~FILE_TYPE_REMOTE;
if (htype == FILE_TYPE_CHAR)
return ReadBufFromConsole(h, pBuf, cch, pcch);
else
return ReadBufFromFile(h, pBuf, cch, pcch);
}