reactos/base/shell/cmd/batch.c

558 lines
14 KiB
C
Raw Normal View History

/*
* 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>)
* Separated 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 hardcoded strings in En.rc
*/
#include "precomp.h"
/* The stack of current batch contexts.
* NULL when no batch is active.
*/
[CMD] Change ERRORLEVEL behaviour for commands ASSOC, PATH, PROMPT and SET. Commands APPEND/DPATH and FTYPE are also concerned by this; however we do not implement them in our CMD.EXE yet. These commands set the ERRORLEVEL differently, whether or not they are run manually from the command-line/from a .BAT file, or from a .CMD file: - From command-line/.BAT file, these commands set the ERRORLEVEL only if an error occurs. So, if two commands are run consecutively and the first one fails, the ERRORLEVEL will remain set even if the second command succeeds. - However, when being run from a .CMD file, these command will always set the ERRORLEVEL. In the example case described above, the second command that succeeds will reset the ERRORLEVEL to 0. This behaviour is determined from the top-level batch/script file being run. This means that, if a .BAT file is first started, then starts a .CMD file, the commands will still behave the .BAT way; on the opposite, if a .CMD file is first started, then starts a .BAT file, these commands will still behave the .CMD way. To implement this we introduce one global BATCH_TYPE enum variable that is initialized to the corresponding batch/script file type when the top-level script is loaded. It is reset to "none" when that script terminates. See https://ss64.com/nt/errorlevel.html for more details, section "Old style .bat Batch files vs .cmd Batch scripts", and https://groups.google.com/forum/#!msg/microsoft.public.win2000.cmdprompt.admin/XHeUq8oe2wk/LIEViGNmkK0J (comment by Mark Zbikowski).
2020-07-01 00:15:52 +00:00
BATCH_TYPE BatType = NONE;
PBATCH_CONTEXT bc = NULL;
#ifdef MSCMD_BATCH_ECHO
BOOL bBcEcho = TRUE;
#endif
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.
*/
BOOL
FindArg(
IN TCHAR Char,
OUT PCTSTR* ArgPtr,
OUT BOOL* IsParam0)
{
PCTSTR pp;
INT n = Char - _T('0');
TRACE("FindArg: (%d)\n", n);
*ArgPtr = NULL;
if (n < 0 || n > 9)
return FALSE;
n = bc->shiftlevel[n];
*IsParam0 = (n == 0);
pp = bc->params;
/* Step up the strings till we reach
* the end or the one we want. */
while (*pp && n--)
pp += _tcslen(pp) + 1;
*ArgPtr = pp;
return TRUE;
}
/*
* Batch_params builds a parameter list in newly allocated memory.
* The parameters consist of null terminated strings with a final
* NULL character signalling the end of the parameters.
*
*/
static 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)
{
WARN("Cannot allocate memory for dp!\n");
error_out_of_memory();
return NULL;
}
if (s1 && *s1)
{
s1 = _stpcpy (dp, s1);
*s1++ = _T('\0');
}
else
s1 = dp;
while (*s2)
{
BOOL inquotes = FALSE;
/* Find next parameter */
while (_istspace(*s2) || (*s2 && _tcschr(STANDARD_SEPS, *s2)))
s2++;
if (!*s2)
break;
/* Copy it */
do
{
if (!inquotes && (_istspace(*s2) || _tcschr(STANDARD_SEPS, *s2)))
break;
inquotes ^= (*s2 == _T('"'));
*s1++ = *s2++;
} while (*s2);
*s1++ = _T('\0');
}
*s1 = _T('\0');
return dp;
}
/*
* Free the allocated memory of a batch file.
*/
static VOID ClearBatch(VOID)
{
TRACE("ClearBatch mem = %08x ; free = %d\n", bc->mem, bc->memfree);
if (bc->mem && bc->memfree)
cmd_free(bc->mem);
if (bc->raw_params)
cmd_free(bc->raw_params);
if (bc->params)
cmd_free(bc->params);
}
/*
* 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(VOID)
{
ClearBatch();
TRACE("ExitBatch\n");
UndoRedirection(bc->RedirList, NULL);
FreeRedirection(bc->RedirList);
#ifndef MSCMD_BATCH_ECHO
/* Preserve echo state across batch calls */
bEcho = bc->bEcho;
#endif
while (bc->setlocal)
cmd_endlocal(_T(""));
bc = bc->prev;
#if 0
/* Do not process any more parts of a compound command */
bc->current = NULL;
#endif
/* If there is no more batch contexts, notify the signal handler */
if (!bc)
[CMD] Change ERRORLEVEL behaviour for commands ASSOC, PATH, PROMPT and SET. Commands APPEND/DPATH and FTYPE are also concerned by this; however we do not implement them in our CMD.EXE yet. These commands set the ERRORLEVEL differently, whether or not they are run manually from the command-line/from a .BAT file, or from a .CMD file: - From command-line/.BAT file, these commands set the ERRORLEVEL only if an error occurs. So, if two commands are run consecutively and the first one fails, the ERRORLEVEL will remain set even if the second command succeeds. - However, when being run from a .CMD file, these command will always set the ERRORLEVEL. In the example case described above, the second command that succeeds will reset the ERRORLEVEL to 0. This behaviour is determined from the top-level batch/script file being run. This means that, if a .BAT file is first started, then starts a .CMD file, the commands will still behave the .BAT way; on the opposite, if a .CMD file is first started, then starts a .BAT file, these commands will still behave the .CMD way. To implement this we introduce one global BATCH_TYPE enum variable that is initialized to the corresponding batch/script file type when the top-level script is loaded. It is reset to "none" when that script terminates. See https://ss64.com/nt/errorlevel.html for more details, section "Old style .bat Batch files vs .cmd Batch scripts", and https://groups.google.com/forum/#!msg/microsoft.public.win2000.cmdprompt.admin/XHeUq8oe2wk/LIEViGNmkK0J (comment by Mark Zbikowski).
2020-07-01 00:15:52 +00:00
{
CheckCtrlBreak(BREAK_OUTOFBATCH);
[CMD] Change ERRORLEVEL behaviour for commands ASSOC, PATH, PROMPT and SET. Commands APPEND/DPATH and FTYPE are also concerned by this; however we do not implement them in our CMD.EXE yet. These commands set the ERRORLEVEL differently, whether or not they are run manually from the command-line/from a .BAT file, or from a .CMD file: - From command-line/.BAT file, these commands set the ERRORLEVEL only if an error occurs. So, if two commands are run consecutively and the first one fails, the ERRORLEVEL will remain set even if the second command succeeds. - However, when being run from a .CMD file, these command will always set the ERRORLEVEL. In the example case described above, the second command that succeeds will reset the ERRORLEVEL to 0. This behaviour is determined from the top-level batch/script file being run. This means that, if a .BAT file is first started, then starts a .CMD file, the commands will still behave the .BAT way; on the opposite, if a .CMD file is first started, then starts a .BAT file, these commands will still behave the .CMD way. To implement this we introduce one global BATCH_TYPE enum variable that is initialized to the corresponding batch/script file type when the top-level script is loaded. It is reset to "none" when that script terminates. See https://ss64.com/nt/errorlevel.html for more details, section "Old style .bat Batch files vs .cmd Batch scripts", and https://groups.google.com/forum/#!msg/microsoft.public.win2000.cmdprompt.admin/XHeUq8oe2wk/LIEViGNmkK0J (comment by Mark Zbikowski).
2020-07-01 00:15:52 +00:00
BatType = NONE;
#ifdef MSCMD_BATCH_ECHO
bEcho = bBcEcho;
#endif
[CMD] Change ERRORLEVEL behaviour for commands ASSOC, PATH, PROMPT and SET. Commands APPEND/DPATH and FTYPE are also concerned by this; however we do not implement them in our CMD.EXE yet. These commands set the ERRORLEVEL differently, whether or not they are run manually from the command-line/from a .BAT file, or from a .CMD file: - From command-line/.BAT file, these commands set the ERRORLEVEL only if an error occurs. So, if two commands are run consecutively and the first one fails, the ERRORLEVEL will remain set even if the second command succeeds. - However, when being run from a .CMD file, these command will always set the ERRORLEVEL. In the example case described above, the second command that succeeds will reset the ERRORLEVEL to 0. This behaviour is determined from the top-level batch/script file being run. This means that, if a .BAT file is first started, then starts a .CMD file, the commands will still behave the .BAT way; on the opposite, if a .CMD file is first started, then starts a .BAT file, these commands will still behave the .CMD way. To implement this we introduce one global BATCH_TYPE enum variable that is initialized to the corresponding batch/script file type when the top-level script is loaded. It is reset to "none" when that script terminates. See https://ss64.com/nt/errorlevel.html for more details, section "Old style .bat Batch files vs .cmd Batch scripts", and https://groups.google.com/forum/#!msg/microsoft.public.win2000.cmdprompt.admin/XHeUq8oe2wk/LIEViGNmkK0J (comment by Mark Zbikowski).
2020-07-01 00:15:52 +00:00
}
}
/*
* Exit all the nested batch calls.
*/
VOID ExitAllBatches(VOID)
{
while (bc)
ExitBatch();
}
/*
* Load batch file into memory.
*/
static void BatchFile2Mem(HANDLE hBatchFile)
{
TRACE("BatchFile2Mem()\n");
bc->memsize = GetFileSize(hBatchFile, NULL);
bc->mem = (char *)cmd_alloc(bc->memsize+1); /* 1 extra for '\0' */
/* if memory is available, read it in and close the file */
if (bc->mem != NULL)
{
TRACE ("BatchFile2Mem memory %08x - %08x\n",bc->mem,bc->memsize);
SetFilePointer (hBatchFile, 0, NULL, FILE_BEGIN);
ReadFile(hBatchFile, (LPVOID)bc->mem, bc->memsize, &bc->memsize, NULL);
bc->mem[bc->memsize]='\0'; /* end this, so you can dump it as a string */
bc->memfree=TRUE; /* this one needs to be freed */
}
else
{
bc->memsize=0; /* this will prevent mem being accessed */
bc->memfree=FALSE;
}
bc->mempos = 0; /* set position to the start */
}
/*
* Start batch file execution.
*
* The firstword parameter is the full filename of the batch file.
*/
INT Batch(LPTSTR fullname, LPTSTR firstword, LPTSTR param, PARSED_COMMAND *Cmd)
{
INT ret = 0;
INT i;
HANDLE hFile = NULL;
[CMD] Change ERRORLEVEL behaviour for commands ASSOC, PATH, PROMPT and SET. Commands APPEND/DPATH and FTYPE are also concerned by this; however we do not implement them in our CMD.EXE yet. These commands set the ERRORLEVEL differently, whether or not they are run manually from the command-line/from a .BAT file, or from a .CMD file: - From command-line/.BAT file, these commands set the ERRORLEVEL only if an error occurs. So, if two commands are run consecutively and the first one fails, the ERRORLEVEL will remain set even if the second command succeeds. - However, when being run from a .CMD file, these command will always set the ERRORLEVEL. In the example case described above, the second command that succeeds will reset the ERRORLEVEL to 0. This behaviour is determined from the top-level batch/script file being run. This means that, if a .BAT file is first started, then starts a .CMD file, the commands will still behave the .BAT way; on the opposite, if a .CMD file is first started, then starts a .BAT file, these commands will still behave the .CMD way. To implement this we introduce one global BATCH_TYPE enum variable that is initialized to the corresponding batch/script file type when the top-level script is loaded. It is reset to "none" when that script terminates. See https://ss64.com/nt/errorlevel.html for more details, section "Old style .bat Batch files vs .cmd Batch scripts", and https://groups.google.com/forum/#!msg/microsoft.public.win2000.cmdprompt.admin/XHeUq8oe2wk/LIEViGNmkK0J (comment by Mark Zbikowski).
2020-07-01 00:15:52 +00:00
BOOLEAN bSameFn = FALSE;
BOOLEAN bTopLevel;
BATCH_CONTEXT new;
PFOR_CONTEXT saved_fc;
SetLastError(0);
if (bc && bc->mem)
{
TCHAR fpname[MAX_PATH];
GetFullPathName(fullname, ARRAYSIZE(fpname), fpname, NULL);
if (_tcsicmp(bc->BatchFilePath, fpname) == 0)
bSameFn = TRUE;
}
TRACE("Batch(\'%s\', \'%s\', \'%s\') bSameFn = %d\n",
debugstr_aw(fullname), debugstr_aw(firstword), debugstr_aw(param), bSameFn);
if (!bSameFn)
{
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);
if (hFile == INVALID_HANDLE_VALUE)
{
ConErrResPuts(STRING_BATCH_ERROR);
return 1;
}
}
[CMD] Change ERRORLEVEL behaviour for commands ASSOC, PATH, PROMPT and SET. Commands APPEND/DPATH and FTYPE are also concerned by this; however we do not implement them in our CMD.EXE yet. These commands set the ERRORLEVEL differently, whether or not they are run manually from the command-line/from a .BAT file, or from a .CMD file: - From command-line/.BAT file, these commands set the ERRORLEVEL only if an error occurs. So, if two commands are run consecutively and the first one fails, the ERRORLEVEL will remain set even if the second command succeeds. - However, when being run from a .CMD file, these command will always set the ERRORLEVEL. In the example case described above, the second command that succeeds will reset the ERRORLEVEL to 0. This behaviour is determined from the top-level batch/script file being run. This means that, if a .BAT file is first started, then starts a .CMD file, the commands will still behave the .BAT way; on the opposite, if a .CMD file is first started, then starts a .BAT file, these commands will still behave the .CMD way. To implement this we introduce one global BATCH_TYPE enum variable that is initialized to the corresponding batch/script file type when the top-level script is loaded. It is reset to "none" when that script terminates. See https://ss64.com/nt/errorlevel.html for more details, section "Old style .bat Batch files vs .cmd Batch scripts", and https://groups.google.com/forum/#!msg/microsoft.public.win2000.cmdprompt.admin/XHeUq8oe2wk/LIEViGNmkK0J (comment by Mark Zbikowski).
2020-07-01 00:15:52 +00:00
/*
* Remember whether this is a top-level batch context, i.e. if there is
* no batch context existing prior (bc == NULL originally), and we are
* going to create one below.
*/
bTopLevel = !bc;
if (bc != NULL && Cmd == bc->current)
{
/* Then we are transferring to another batch */
ClearBatch();
AddBatchRedirection(&Cmd->Redirections);
}
else
{
struct _SETLOCAL *setlocal = NULL;
if (Cmd == NULL)
{
/* This is a CALL. CALL will set errorlevel to our return value, so
* in order to keep the value of errorlevel unchanged in the case
* of calling an empty batch file, we must return that same value. */
ret = nErrorLevel;
}
else if (bc)
{
/* If a batch file runs another batch file as part of a compound command
* (e.g. "x.bat & somethingelse") then the first file gets terminated. */
/* Get its SETLOCAL stack so it can be migrated to the new context */
setlocal = bc->setlocal;
bc->setlocal = NULL;
ExitBatch();
}
/* Create a new context. This function will not
* return until this context has been exited */
new.prev = bc;
/* copy some fields in the new structure if it is the same file */
if (bSameFn)
{
new.mem = bc->mem;
new.memsize = bc->memsize;
new.mempos = 0;
new.memfree = FALSE; /* don't free this, being used before this */
}
bc = &new;
bc->RedirList = NULL;
bc->setlocal = setlocal;
}
GetFullPathName(fullname, ARRAYSIZE(bc->BatchFilePath), bc->BatchFilePath, NULL);
/* If a new batch file, load it into memory and close the file */
if (!bSameFn)
{
BatchFile2Mem(hFile);
CloseHandle(hFile);
}
bc->mempos = 0; /* Go to the beginning of the batch file */
#ifndef MSCMD_BATCH_ECHO
bc->bEcho = bEcho; /* Preserve echo across batch calls */
#endif
for (i = 0; i < 10; i++)
bc->shiftlevel[i] = i;
/* Parse the parameters and make a raw copy of them without modifications */
bc->params = BatchParams(firstword, param);
bc->raw_params = cmd_dup(param);
if (bc->raw_params == NULL)
{
error_out_of_memory();
return 1;
}
/* If we are calling from inside a FOR, hide the FOR variables */
saved_fc = fc;
fc = NULL;
[CMD] Change ERRORLEVEL behaviour for commands ASSOC, PATH, PROMPT and SET. Commands APPEND/DPATH and FTYPE are also concerned by this; however we do not implement them in our CMD.EXE yet. These commands set the ERRORLEVEL differently, whether or not they are run manually from the command-line/from a .BAT file, or from a .CMD file: - From command-line/.BAT file, these commands set the ERRORLEVEL only if an error occurs. So, if two commands are run consecutively and the first one fails, the ERRORLEVEL will remain set even if the second command succeeds. - However, when being run from a .CMD file, these command will always set the ERRORLEVEL. In the example case described above, the second command that succeeds will reset the ERRORLEVEL to 0. This behaviour is determined from the top-level batch/script file being run. This means that, if a .BAT file is first started, then starts a .CMD file, the commands will still behave the .BAT way; on the opposite, if a .CMD file is first started, then starts a .BAT file, these commands will still behave the .CMD way. To implement this we introduce one global BATCH_TYPE enum variable that is initialized to the corresponding batch/script file type when the top-level script is loaded. It is reset to "none" when that script terminates. See https://ss64.com/nt/errorlevel.html for more details, section "Old style .bat Batch files vs .cmd Batch scripts", and https://groups.google.com/forum/#!msg/microsoft.public.win2000.cmdprompt.admin/XHeUq8oe2wk/LIEViGNmkK0J (comment by Mark Zbikowski).
2020-07-01 00:15:52 +00:00
/* Perform top-level batch initialization */
if (bTopLevel)
{
/* Default the top-level batch context type to .BAT */
BatType = BAT_TYPE;
/* If this is a .CMD file, adjust the type */
TCHAR *dot = _tcsrchr(bc->BatchFilePath, _T('.'));
if (dot && (!_tcsicmp(dot, _T(".cmd"))))
{
BatType = CMD_TYPE;
}
#ifdef MSCMD_BATCH_ECHO
bBcEcho = bEcho;
#endif
[CMD] Change ERRORLEVEL behaviour for commands ASSOC, PATH, PROMPT and SET. Commands APPEND/DPATH and FTYPE are also concerned by this; however we do not implement them in our CMD.EXE yet. These commands set the ERRORLEVEL differently, whether or not they are run manually from the command-line/from a .BAT file, or from a .CMD file: - From command-line/.BAT file, these commands set the ERRORLEVEL only if an error occurs. So, if two commands are run consecutively and the first one fails, the ERRORLEVEL will remain set even if the second command succeeds. - However, when being run from a .CMD file, these command will always set the ERRORLEVEL. In the example case described above, the second command that succeeds will reset the ERRORLEVEL to 0. This behaviour is determined from the top-level batch/script file being run. This means that, if a .BAT file is first started, then starts a .CMD file, the commands will still behave the .BAT way; on the opposite, if a .CMD file is first started, then starts a .BAT file, these commands will still behave the .CMD way. To implement this we introduce one global BATCH_TYPE enum variable that is initialized to the corresponding batch/script file type when the top-level script is loaded. It is reset to "none" when that script terminates. See https://ss64.com/nt/errorlevel.html for more details, section "Old style .bat Batch files vs .cmd Batch scripts", and https://groups.google.com/forum/#!msg/microsoft.public.win2000.cmdprompt.admin/XHeUq8oe2wk/LIEViGNmkK0J (comment by Mark Zbikowski).
2020-07-01 00:15:52 +00:00
}
/* Check if this is a "CALL :label" */
if (*firstword == _T(':'))
ret = cmd_goto(firstword);
/* If we have created a new context, don't return
* until this batch file has completed. */
while (bc == &new && !bExit)
{
Cmd = ParseCommand(NULL);
if (!Cmd)
{
if (!bParseError)
continue;
/* Echo the pre-parsed batch file line on error */
if (bEcho && !bDisableBatchEcho)
{
if (!bIgnoreEcho)
ConOutChar(_T('\n'));
PrintPrompt();
ConOutPuts(ParseLine);
ConOutChar(_T('\n'));
}
/* Stop all execution */
ExitAllBatches();
ret = 1;
break;
}
/* JPP 19980807 */
/* Echo the command and execute it */
bc->current = Cmd;
ret = ExecuteCommandWithEcho(Cmd);
FreeCommand(Cmd);
}
[CMD] Change ERRORLEVEL behaviour for commands ASSOC, PATH, PROMPT and SET. Commands APPEND/DPATH and FTYPE are also concerned by this; however we do not implement them in our CMD.EXE yet. These commands set the ERRORLEVEL differently, whether or not they are run manually from the command-line/from a .BAT file, or from a .CMD file: - From command-line/.BAT file, these commands set the ERRORLEVEL only if an error occurs. So, if two commands are run consecutively and the first one fails, the ERRORLEVEL will remain set even if the second command succeeds. - However, when being run from a .CMD file, these command will always set the ERRORLEVEL. In the example case described above, the second command that succeeds will reset the ERRORLEVEL to 0. This behaviour is determined from the top-level batch/script file being run. This means that, if a .BAT file is first started, then starts a .CMD file, the commands will still behave the .BAT way; on the opposite, if a .CMD file is first started, then starts a .BAT file, these commands will still behave the .CMD way. To implement this we introduce one global BATCH_TYPE enum variable that is initialized to the corresponding batch/script file type when the top-level script is loaded. It is reset to "none" when that script terminates. See https://ss64.com/nt/errorlevel.html for more details, section "Old style .bat Batch files vs .cmd Batch scripts", and https://groups.google.com/forum/#!msg/microsoft.public.win2000.cmdprompt.admin/XHeUq8oe2wk/LIEViGNmkK0J (comment by Mark Zbikowski).
2020-07-01 00:15:52 +00:00
/* Perform top-level batch cleanup */
if (!bc || bTopLevel)
{
/* Reset the top-level batch context type */
BatType = NONE;
#ifdef MSCMD_BATCH_ECHO
bEcho = bBcEcho;
#endif
[CMD] Change ERRORLEVEL behaviour for commands ASSOC, PATH, PROMPT and SET. Commands APPEND/DPATH and FTYPE are also concerned by this; however we do not implement them in our CMD.EXE yet. These commands set the ERRORLEVEL differently, whether or not they are run manually from the command-line/from a .BAT file, or from a .CMD file: - From command-line/.BAT file, these commands set the ERRORLEVEL only if an error occurs. So, if two commands are run consecutively and the first one fails, the ERRORLEVEL will remain set even if the second command succeeds. - However, when being run from a .CMD file, these command will always set the ERRORLEVEL. In the example case described above, the second command that succeeds will reset the ERRORLEVEL to 0. This behaviour is determined from the top-level batch/script file being run. This means that, if a .BAT file is first started, then starts a .CMD file, the commands will still behave the .BAT way; on the opposite, if a .CMD file is first started, then starts a .BAT file, these commands will still behave the .CMD way. To implement this we introduce one global BATCH_TYPE enum variable that is initialized to the corresponding batch/script file type when the top-level script is loaded. It is reset to "none" when that script terminates. See https://ss64.com/nt/errorlevel.html for more details, section "Old style .bat Batch files vs .cmd Batch scripts", and https://groups.google.com/forum/#!msg/microsoft.public.win2000.cmdprompt.admin/XHeUq8oe2wk/LIEViGNmkK0J (comment by Mark Zbikowski).
2020-07-01 00:15:52 +00:00
}
/* Restore the FOR variables */
fc = saved_fc;
/* Always return the last command's return code */
TRACE("Batch: returns %d\n", ret);
return ret;
}
VOID AddBatchRedirection(REDIRECTION **RedirList)
{
REDIRECTION **ListEnd;
/* Prepend the list to the batch context's list */
ListEnd = RedirList;
while (*ListEnd)
ListEnd = &(*ListEnd)->Next;
*ListEnd = bc->RedirList;
bc->RedirList = *RedirList;
/* Null out the pointer so that the list will not be cleared prematurely.
* These redirections should persist until the batch file exits. */
*RedirList = NULL;
}
/*
* Read a single line from the batch file from the current batch/memory position.
* Almost a copy of FileGetString with same UNICODE handling
*/
BOOL BatchGetString(LPTSTR lpBuffer, INT nBufferLength)
{
LPSTR lpString;
INT len = 0;
#ifdef _UNICODE
lpString = cmd_alloc(nBufferLength);
if (!lpString)
{
WARN("Cannot allocate memory for lpString\n");
error_out_of_memory();
return FALSE;
}
#else
lpString = lpBuffer;
#endif
/* read all chars from memory until a '\n' is encountered */
if (bc->mem)
{
for (; (bc->mempos < bc->memsize && len < (nBufferLength-1)); len++)
{
lpString[len] = bc->mem[bc->mempos++];
if (lpString[len] == '\n' )
{
len++;
break;
}
}
}
if (!len)
{
#ifdef _UNICODE
cmd_free(lpString);
#endif
return FALSE;
}
lpString[len++] = '\0';
#ifdef _UNICODE
MultiByteToWideChar(OutputCodePage, 0, lpString, -1, lpBuffer, len);
cmd_free(lpString);
#endif
return TRUE;
}
/*
* 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.
*
* Set eflag to 0 if line is not to be echoed else 1
*/
LPTSTR ReadBatchLine(VOID)
{
TRACE("ReadBatchLine()\n");
/* User halt */
if (CheckCtrlBreak(BREAK_BATCHFILE))
{
ExitAllBatches();
return NULL;
}
if (!BatchGetString(textline, ARRAYSIZE(textline) - 1))
{
TRACE("ReadBatchLine(): Reached EOF!\n");
/* End of file */
ExitBatch();
return NULL;
}
TRACE("ReadBatchLine(): textline: \'%s\'\n", debugstr_aw(textline));
if (textline[_tcslen(textline) - 1] != _T('\n'))
_tcscat(textline, _T("\n"));
return textline;
}
/* EOF */