mirror of
https://github.com/reactos/reactos.git
synced 2024-09-20 01:31:34 +00:00
c501d8112c
svn path=/branches/aicom-network-fixes/; revision=34994
507 lines
11 KiB
C
507 lines
11 KiB
C
/*
|
|
* BATCH.C - batch file processor for CMD.EXE.
|
|
*
|
|
*
|
|
* History:
|
|
*
|
|
* ??/??/?? (Evan Jeffrey)
|
|
* started.
|
|
*
|
|
* 15 Jul 1995 (Tim Norman)
|
|
* modes and bugfixes.
|
|
*
|
|
* 08 Aug 1995 (Matt Rains)
|
|
* i have cleaned up the source code. changes now bring this
|
|
* source into guidelines for recommended programming practice.
|
|
*
|
|
* i have added some constants to help making changes easier.
|
|
*
|
|
* 29 Jan 1996 (Steffan Kaiser)
|
|
* made a few cosmetic changes
|
|
*
|
|
* 05 Feb 1996 (Tim Norman)
|
|
* changed to comply with new first/rest calling scheme
|
|
*
|
|
* 14 Jun 1997 (Steffen Kaiser)
|
|
* bug fixes. added error level expansion %?. ctrl-break handling
|
|
*
|
|
* 16 Jul 1998 (Hans B Pufal)
|
|
* Totally reorganised in conjunction with COMMAND.C (cf) to
|
|
* implement proper BATCH file nesting and other improvements.
|
|
*
|
|
* 16 Jul 1998 (John P Price <linux-guru@gcfl.net>)
|
|
* Seperated commands into individual files.
|
|
*
|
|
* 19 Jul 1998 (Hans B Pufal) [HBP_001]
|
|
* Preserve state of echo flag across batch calls.
|
|
*
|
|
* 19 Jul 1998 (Hans B Pufal) [HBP_002]
|
|
* Implementation of FOR command
|
|
*
|
|
* 20-Jul-1998 (John P Price <linux-guru@gcfl.net>)
|
|
* added error checking after cmd_alloc calls
|
|
*
|
|
* 27-Jul-1998 (John P Price <linux-guru@gcfl.net>)
|
|
* added config.h include
|
|
*
|
|
* 02-Aug-1998 (Hans B Pufal) [HBP_003]
|
|
* Fixed bug in ECHO flag restoration at exit from batch file
|
|
*
|
|
* 26-Jan-1999 Eric Kohl
|
|
* Replaced CRT io functions by Win32 io functions.
|
|
* Unicode safe!
|
|
*
|
|
* 23-Feb-2001 (Carl Nettelblad <cnettel@hem.passagen.es>)
|
|
* Fixes made to get "for" working.
|
|
*
|
|
* 02-Apr-2005 (Magnus Olsen) <magnus@greatlord.com>)
|
|
* Remove all hardcode string to En.rc
|
|
*/
|
|
|
|
#include <precomp.h>
|
|
|
|
|
|
/* The stack of current batch contexts.
|
|
* NULL when no batch is active
|
|
*/
|
|
LPBATCH_CONTEXT bc = NULL;
|
|
|
|
BOOL bEcho = TRUE; /* The echo flag */
|
|
|
|
|
|
|
|
/* Buffer for reading Batch file lines */
|
|
TCHAR textline[BATCH_BUFFSIZE];
|
|
|
|
|
|
/*
|
|
* Returns a pointer to the n'th parameter of the current batch file.
|
|
* If no such parameter exists returns pointer to empty string.
|
|
* If no batch file is current, returns NULL
|
|
*
|
|
*/
|
|
|
|
LPTSTR FindArg (INT n)
|
|
{
|
|
LPTSTR pp;
|
|
|
|
TRACE ("FindArg: (%d)\n", n);
|
|
|
|
if (bc == NULL)
|
|
return NULL;
|
|
|
|
n += bc->shiftlevel;
|
|
pp = bc->params;
|
|
|
|
/* Step up the strings till we reach the end */
|
|
/* or the one we want */
|
|
while (*pp && n--)
|
|
pp += _tcslen (pp) + 1;
|
|
|
|
return pp;
|
|
}
|
|
|
|
|
|
/*
|
|
* Batch_params builds a parameter list in newlay allocated memory.
|
|
* The parameters consist of null terminated strings with a final
|
|
* NULL character signalling the end of the parameters.
|
|
*
|
|
*/
|
|
|
|
LPTSTR BatchParams (LPTSTR s1, LPTSTR s2)
|
|
{
|
|
LPTSTR dp = (LPTSTR)cmd_alloc ((_tcslen(s1) + _tcslen(s2) + 3) * sizeof (TCHAR));
|
|
|
|
/* JPP 20-Jul-1998 added error checking */
|
|
if (dp == NULL)
|
|
{
|
|
error_out_of_memory();
|
|
return NULL;
|
|
}
|
|
|
|
if (s1 && *s1)
|
|
{
|
|
s1 = _stpcpy (dp, s1);
|
|
*s1++ = _T('\0');
|
|
}
|
|
else
|
|
s1 = dp;
|
|
|
|
while (*s2)
|
|
{
|
|
if (_istspace (*s2) || _tcschr (_T(",;"), *s2))
|
|
{
|
|
*s1++ = _T('\0');
|
|
s2++;
|
|
while (*s2 && _tcschr (_T(" ,;"), *s2))
|
|
s2++;
|
|
continue;
|
|
}
|
|
|
|
if ((*s2 == _T('"')) || (*s2 == _T('\'')))
|
|
{
|
|
TCHAR st = *s2;
|
|
|
|
do
|
|
*s1++ = *s2++;
|
|
while (*s2 && (*s2 != st));
|
|
}
|
|
|
|
*s1++ = *s2++;
|
|
}
|
|
|
|
*s1++ = _T('\0');
|
|
*s1 = _T('\0');
|
|
|
|
return dp;
|
|
}
|
|
|
|
|
|
/*
|
|
* If a batch file is current, exits it, freeing the context block and
|
|
* chaining back to the previous one.
|
|
*
|
|
* If no new batch context is found, sets ECHO back ON.
|
|
*
|
|
* If the parameter is non-null or not empty, it is printed as an exit
|
|
* message
|
|
*/
|
|
|
|
VOID ExitBatch (LPTSTR msg)
|
|
{
|
|
TRACE ("ExitBatch: (\'%s\')\n", debugstr_aw(msg));
|
|
|
|
if (bc != NULL)
|
|
{
|
|
LPBATCH_CONTEXT t = bc;
|
|
|
|
if (bc->hBatchFile)
|
|
{
|
|
CloseHandle (bc->hBatchFile);
|
|
bc->hBatchFile = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
if (bc->raw_params)
|
|
cmd_free(bc->raw_params);
|
|
|
|
if (bc->params)
|
|
cmd_free(bc->params);
|
|
|
|
if (bc->forproto)
|
|
cmd_free(bc->forproto);
|
|
|
|
if (bc->ffind)
|
|
cmd_free(bc->ffind);
|
|
|
|
/* Preserve echo state across batch calls */
|
|
bEcho = bc->bEcho;
|
|
|
|
bc = bc->prev;
|
|
cmd_free(t);
|
|
}
|
|
|
|
if (msg && *msg)
|
|
ConOutPrintf (_T("%s\n"), msg);
|
|
}
|
|
|
|
|
|
/*
|
|
* Start batch file execution
|
|
*
|
|
* The firstword parameter is the full filename of the batch file.
|
|
*
|
|
*/
|
|
|
|
BOOL Batch (LPTSTR fullname, LPTSTR firstword, LPTSTR param)
|
|
{
|
|
HANDLE hFile;
|
|
LPTSTR tmp;
|
|
SetLastError(0);
|
|
hFile = CreateFile (fullname, GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, NULL,
|
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL |
|
|
FILE_FLAG_SEQUENTIAL_SCAN, NULL);
|
|
|
|
TRACE ("Batch: (\'%s\', \'%s\', \'%s\') hFile = %x\n",
|
|
debugstr_aw(fullname), debugstr_aw(firstword), debugstr_aw(param), hFile);
|
|
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
ConErrResPuts(STRING_BATCH_ERROR);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Kill any and all FOR contexts */
|
|
while (bc && bc->forvar)
|
|
ExitBatch (NULL);
|
|
|
|
if (bc == NULL)
|
|
{
|
|
/* No curent batch file, create a new context */
|
|
LPBATCH_CONTEXT n = (LPBATCH_CONTEXT)cmd_alloc (sizeof(BATCH_CONTEXT));
|
|
|
|
if (n == NULL)
|
|
{
|
|
error_out_of_memory ();
|
|
return FALSE;
|
|
}
|
|
|
|
n->prev = bc;
|
|
bc = n;
|
|
bc->In[0] = _T('\0');
|
|
bc->Out[0] = _T('\0');
|
|
bc->Err[0] = _T('\0');
|
|
}
|
|
else if (bc->hBatchFile != INVALID_HANDLE_VALUE)
|
|
{
|
|
/* Then we are transferring to another batch */
|
|
CloseHandle (bc->hBatchFile);
|
|
bc->hBatchFile = INVALID_HANDLE_VALUE;
|
|
if (bc->params)
|
|
cmd_free (bc->params);
|
|
if (bc->raw_params)
|
|
cmd_free (bc->raw_params);
|
|
}
|
|
|
|
ZeroMemory(bc->BatchFilePath, sizeof(bc->BatchFilePath));
|
|
bc->hBatchFile = hFile;
|
|
tmp = _tcsrchr(fullname, '\\');
|
|
_tcsncpy(bc->BatchFilePath, fullname, ((_tcslen(fullname) - _tcslen(tmp)) + 1));
|
|
SetFilePointer (bc->hBatchFile, 0, NULL, FILE_BEGIN);
|
|
bc->bEcho = bEcho; /* Preserve echo across batch calls */
|
|
bc->shiftlevel = 0;
|
|
bc->bCmdBlock = -1;
|
|
bc->lCallPosition = 0;
|
|
bc->lCallPositionHigh = 0;
|
|
|
|
bc->ffind = NULL;
|
|
bc->forvar = _T('\0');
|
|
bc->forproto = NULL;
|
|
bc->params = BatchParams (firstword, param);
|
|
//
|
|
// Allocate enough memory to hold the params and copy them over without modifications
|
|
//
|
|
bc->raw_params = (TCHAR*) cmd_alloc((_tcslen(param)+1) * sizeof(TCHAR));
|
|
if (bc->raw_params != NULL)
|
|
{
|
|
_tcscpy(bc->raw_params,param);
|
|
}
|
|
else
|
|
{
|
|
error_out_of_memory();
|
|
return FALSE;
|
|
}
|
|
|
|
/* Don't print a newline for this command */
|
|
bIgnoreEcho = TRUE;
|
|
|
|
TRACE ("Batch: returns TRUE\n");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
VOID AddBatchRedirection(TCHAR * ifn, TCHAR * ofn, TCHAR * efn)
|
|
{
|
|
if(!bc)
|
|
return;
|
|
if(_tcslen(ifn))
|
|
_tcscpy(bc->In,ifn);
|
|
if(_tcslen(ofn))
|
|
_tcscpy(bc->Out,ofn);
|
|
if(_tcslen(efn))
|
|
_tcscpy(bc->Err,efn);
|
|
|
|
}
|
|
|
|
/*
|
|
* Read and return the next executable line form the current batch file
|
|
*
|
|
* If no batch file is current or no further executable lines are found
|
|
* return NULL.
|
|
*
|
|
* Here we also look out for FOR bcontext structures which trigger the
|
|
* FOR expansion code.
|
|
*
|
|
* Set eflag to 0 if line is not to be echoed else 1
|
|
*/
|
|
|
|
LPTSTR ReadBatchLine (LPBOOL bLocalEcho)
|
|
{
|
|
LPTSTR first;
|
|
LPTSTR ip;
|
|
|
|
/* No batch */
|
|
if (bc == NULL)
|
|
return NULL;
|
|
|
|
TRACE ("ReadBatchLine ()\n");
|
|
|
|
while (1)
|
|
{
|
|
/* User halt */
|
|
if (CheckCtrlBreak (BREAK_BATCHFILE))
|
|
{
|
|
while (bc)
|
|
ExitBatch (NULL);
|
|
return NULL;
|
|
}
|
|
|
|
/* No batch */
|
|
if (bc == NULL)
|
|
return NULL;
|
|
|
|
/* If its a FOR context... */
|
|
if (bc->forvar)
|
|
{
|
|
LPTSTR sp = bc->forproto; /* pointer to prototype command */
|
|
LPTSTR dp = textline; /* Place to expand protoype */
|
|
LPTSTR fv = FindArg (0); /* Next list element */
|
|
|
|
/* End of list so... */
|
|
if ((fv == NULL) || (*fv == _T('\0')))
|
|
{
|
|
/* just exit this context */
|
|
ExitBatch (NULL);
|
|
continue;
|
|
}
|
|
|
|
if (_tcscspn (fv, _T("?*")) == _tcslen (fv))
|
|
{
|
|
/* element is wild file */
|
|
bc->shiftlevel++; /* No use it and shift list */
|
|
}
|
|
else
|
|
{
|
|
/* Wild file spec, find first (or next) file name */
|
|
if (bc->ffind)
|
|
{
|
|
/* First already done so do next */
|
|
|
|
fv = FindNextFile (bc->hFind, bc->ffind) ? bc->ffind->cFileName : NULL;
|
|
}
|
|
else
|
|
{
|
|
/* For first find, allocate a find first block */
|
|
if ((bc->ffind = (LPWIN32_FIND_DATA)cmd_alloc (sizeof (WIN32_FIND_DATA))) == NULL)
|
|
{
|
|
error_out_of_memory();
|
|
return NULL;
|
|
}
|
|
|
|
bc->hFind = FindFirstFile (fv, bc->ffind);
|
|
|
|
fv = !(bc->hFind==INVALID_HANDLE_VALUE) ? bc->ffind->cFileName : NULL;
|
|
}
|
|
|
|
if (fv == NULL)
|
|
{
|
|
/* Null indicates no more files.. */
|
|
cmd_free (bc->ffind); /* free the buffer */
|
|
bc->ffind = NULL;
|
|
bc->shiftlevel++; /* On to next list element */
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* At this point, fv points to parameter string */
|
|
while (*sp)
|
|
{
|
|
if ((*sp == _T('%')) && (*(sp + 1) == bc->forvar))
|
|
{
|
|
/* replace % var */
|
|
dp = _stpcpy (dp, fv);
|
|
sp += 2;
|
|
}
|
|
else
|
|
{
|
|
/* Else just copy */
|
|
*dp++ = *sp++;
|
|
}
|
|
}
|
|
|
|
*dp = _T('\0');
|
|
|
|
*bLocalEcho = bEcho;
|
|
|
|
return textline;
|
|
}
|
|
|
|
if (!FileGetString (bc->hBatchFile, textline, sizeof (textline) / sizeof (textline[0])))
|
|
{
|
|
TRACE ("ReadBatchLine(): Reached EOF!\n");
|
|
/* End of file.... */
|
|
ExitBatch (NULL);
|
|
|
|
if (bc == NULL)
|
|
return NULL;
|
|
|
|
continue;
|
|
}
|
|
TRACE ("ReadBatchLine(): textline: \'%s\'\n", debugstr_aw(textline));
|
|
|
|
/* Strip leading spaces and trailing space/control chars */
|
|
for (first = textline; _istspace (*first); first++)
|
|
;
|
|
|
|
for (ip = first + _tcslen (first) - 1; _istspace (*ip) || _istcntrl (*ip); ip--)
|
|
;
|
|
|
|
*++ip = _T('\0');
|
|
|
|
/* cmd block over multiple lines (..) */
|
|
if (bc->bCmdBlock >= 0)
|
|
{
|
|
if (*first == _T(')'))
|
|
{
|
|
first++;
|
|
/* Strip leading spaces and trailing space/control chars */
|
|
while(_istspace (*first))
|
|
first++;
|
|
if ((_tcsncicmp (first, _T("else"), 4) == 0) && (_tcschr(first, _T('('))))
|
|
{
|
|
bc->bExecuteBlock[bc->bCmdBlock] = !bc->bExecuteBlock[bc->bCmdBlock];
|
|
}
|
|
else
|
|
{
|
|
bc->bCmdBlock--;
|
|
}
|
|
continue;
|
|
}
|
|
if (bc->bCmdBlock < MAX_PATH)
|
|
if (!bc->bExecuteBlock[bc->bCmdBlock])
|
|
{
|
|
/* increase the bCmdBlock count when there is another conditon which opens a new bracket */
|
|
if ((_tcsncicmp (first, _T("if"), 2) == 0) && _tcschr(first, _T('(')))
|
|
{
|
|
bc->bCmdBlock++;
|
|
if ((bc->bCmdBlock > 0) && (bc->bCmdBlock < MAX_PATH))
|
|
bc->bExecuteBlock[bc->bCmdBlock] = bc->bExecuteBlock[bc->bCmdBlock - 1];
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* ignore labels and empty lines */
|
|
if (*first == _T(':') || *first == 0)
|
|
continue;
|
|
|
|
if (*first == _T('@'))
|
|
{
|
|
/* don't echo this line */
|
|
do
|
|
first++;
|
|
while (_istspace (*first));
|
|
|
|
*bLocalEcho = 0;
|
|
}
|
|
else
|
|
*bLocalEcho = bEcho;
|
|
|
|
break;
|
|
}
|
|
|
|
return first;
|
|
}
|
|
|
|
/* EOF */
|