2006-02-16 23:23:37 +00:00
|
|
|
/*
|
|
|
|
* 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>)
|
2016-11-05 14:55:55 +00:00
|
|
|
* Separated commands into individual files.
|
2006-02-16 23:23:37 +00:00
|
|
|
*
|
|
|
|
* 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>)
|
2007-08-01 10:17:13 +00:00
|
|
|
* added error checking after cmd_alloc calls
|
2006-02-16 23:23:37 +00:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*
|
2007-05-05 11:32:25 +00:00
|
|
|
* 26-Jan-1999 Eric Kohl
|
2006-02-16 23:23:37 +00:00
|
|
|
* 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.
|
|
|
|
*
|
2013-06-30 12:52:54 +00:00
|
|
|
* 02-Apr-2005 (Magnus Olsen <magnus@greatlord.com>)
|
|
|
|
* Remove all hardcoded strings in En.rc
|
2006-02-16 23:23:37 +00:00
|
|
|
*/
|
|
|
|
|
2013-01-24 23:00:42 +00:00
|
|
|
#include "precomp.h"
|
2006-02-16 23:23:37 +00:00
|
|
|
|
|
|
|
/* The stack of current batch contexts.
|
2020-07-26 18:30:03 +00:00
|
|
|
* NULL when no batch is active.
|
2006-02-16 23:23:37 +00:00
|
|
|
*/
|
[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;
|
2020-07-26 18:30:03 +00:00
|
|
|
PBATCH_CONTEXT bc = NULL;
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2020-07-18 21:41:56 +00:00
|
|
|
#ifdef MSCMD_BATCH_ECHO
|
|
|
|
BOOL bBcEcho = TRUE;
|
|
|
|
#endif
|
|
|
|
|
2013-06-30 12:52:54 +00:00
|
|
|
BOOL bEcho = TRUE; /* The echo flag */
|
2006-02-16 23:23:37 +00:00
|
|
|
|
|
|
|
/* 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.
|
2020-07-04 15:40:58 +00:00
|
|
|
* If no batch file is current, returns NULL.
|
2006-02-16 23:23:37 +00:00
|
|
|
*/
|
2020-07-04 15:40:58 +00:00
|
|
|
BOOL
|
|
|
|
FindArg(
|
|
|
|
IN TCHAR Char,
|
|
|
|
OUT PCTSTR* ArgPtr,
|
|
|
|
OUT BOOL* IsParam0)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2020-07-04 15:40:58 +00:00
|
|
|
PCTSTR pp;
|
2013-06-30 12:52:54 +00:00
|
|
|
INT n = Char - _T('0');
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2020-07-04 15:40:58 +00:00
|
|
|
TRACE("FindArg: (%d)\n", n);
|
|
|
|
|
|
|
|
*ArgPtr = NULL;
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2013-06-30 12:52:54 +00:00
|
|
|
if (n < 0 || n > 9)
|
2020-07-04 15:40:58 +00:00
|
|
|
return FALSE;
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2013-06-30 12:52:54 +00:00
|
|
|
n = bc->shiftlevel[n];
|
|
|
|
*IsParam0 = (n == 0);
|
|
|
|
pp = bc->params;
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2020-07-04 15:40:58 +00:00
|
|
|
/* Step up the strings till we reach
|
|
|
|
* the end or the one we want. */
|
2013-06-30 12:52:54 +00:00
|
|
|
while (*pp && n--)
|
2020-07-04 15:40:58 +00:00
|
|
|
pp += _tcslen(pp) + 1;
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2020-07-04 15:40:58 +00:00
|
|
|
*ArgPtr = pp;
|
|
|
|
return TRUE;
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
2020-08-09 19:13:13 +00:00
|
|
|
* Builds the batch parameter list in newly allocated memory.
|
|
|
|
* The parameters consist of NULL terminated strings with a
|
|
|
|
* final NULL character signalling the end of the parameters.
|
2013-06-30 12:52:54 +00:00
|
|
|
*/
|
2020-08-09 19:13:13 +00:00
|
|
|
static BOOL
|
|
|
|
BatchParams(
|
|
|
|
IN PCTSTR Arg0,
|
|
|
|
IN PCTSTR Args,
|
|
|
|
OUT PTSTR* RawParams,
|
|
|
|
OUT PTSTR* ParamList)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2020-08-09 19:13:13 +00:00
|
|
|
PTSTR dp;
|
|
|
|
SIZE_T len;
|
|
|
|
|
|
|
|
*RawParams = NULL;
|
|
|
|
*ParamList = NULL;
|
|
|
|
|
|
|
|
/* Make a raw copy of the parameters, but trim any leading and trailing whitespace */
|
|
|
|
// Args += _tcsspn(Args, _T(" \t"));
|
|
|
|
while (_istspace(*Args))
|
|
|
|
++Args;
|
|
|
|
dp = (PTSTR)Args + _tcslen(Args);
|
|
|
|
while ((dp > Args) && _istspace(*(dp - 1)))
|
|
|
|
--dp;
|
|
|
|
len = dp - Args;
|
|
|
|
*RawParams = (PTSTR)cmd_alloc((len + 1)* sizeof(TCHAR));
|
|
|
|
if (!*RawParams)
|
|
|
|
{
|
|
|
|
WARN("Cannot allocate memory for RawParams!\n");
|
|
|
|
error_out_of_memory();
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
_tcsncpy(*RawParams, Args, len);
|
|
|
|
(*RawParams)[len] = _T('\0');
|
|
|
|
|
|
|
|
/* Parse the parameters as well */
|
|
|
|
Args = *RawParams;
|
2013-06-30 12:52:54 +00:00
|
|
|
|
2020-08-09 19:13:13 +00:00
|
|
|
*ParamList = (PTSTR)cmd_alloc((_tcslen(Arg0) + _tcslen(Args) + 3) * sizeof(TCHAR));
|
|
|
|
if (!*ParamList)
|
2013-06-30 12:52:54 +00:00
|
|
|
{
|
2020-08-09 19:13:13 +00:00
|
|
|
WARN("Cannot allocate memory for ParamList!\n");
|
2013-06-30 12:52:54 +00:00
|
|
|
error_out_of_memory();
|
2020-08-09 19:13:13 +00:00
|
|
|
cmd_free(*RawParams);
|
|
|
|
*RawParams = NULL;
|
|
|
|
return FALSE;
|
2013-06-30 12:52:54 +00:00
|
|
|
}
|
|
|
|
|
2020-08-09 19:13:13 +00:00
|
|
|
dp = *ParamList;
|
|
|
|
|
|
|
|
if (Arg0 && *Arg0)
|
2013-06-30 12:52:54 +00:00
|
|
|
{
|
2020-08-09 19:13:13 +00:00
|
|
|
dp = _stpcpy(dp, Arg0);
|
|
|
|
*dp++ = _T('\0');
|
2013-06-30 12:52:54 +00:00
|
|
|
}
|
|
|
|
|
2020-08-09 19:13:13 +00:00
|
|
|
while (*Args)
|
2013-06-30 12:52:54 +00:00
|
|
|
{
|
|
|
|
BOOL inquotes = FALSE;
|
|
|
|
|
|
|
|
/* Find next parameter */
|
2020-08-09 19:13:13 +00:00
|
|
|
while (_istspace(*Args) || (*Args && _tcschr(STANDARD_SEPS, *Args)))
|
|
|
|
++Args;
|
|
|
|
if (!*Args)
|
2013-06-30 12:52:54 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
/* Copy it */
|
|
|
|
do
|
|
|
|
{
|
2020-08-09 19:13:13 +00:00
|
|
|
if (!inquotes && (_istspace(*Args) || _tcschr(STANDARD_SEPS, *Args)))
|
2013-06-30 12:52:54 +00:00
|
|
|
break;
|
2020-08-09 19:13:13 +00:00
|
|
|
inquotes ^= (*Args == _T('"'));
|
|
|
|
*dp++ = *Args++;
|
|
|
|
} while (*Args);
|
|
|
|
*dp++ = _T('\0');
|
2013-06-30 12:52:54 +00:00
|
|
|
}
|
2020-08-09 19:13:13 +00:00
|
|
|
*dp = _T('\0');
|
2013-06-30 12:52:54 +00:00
|
|
|
|
2020-08-09 19:13:13 +00:00
|
|
|
return TRUE;
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
2011-06-01 19:33:20 +00:00
|
|
|
/*
|
2020-07-26 18:30:03 +00:00
|
|
|
* Free the allocated memory of a batch file.
|
2011-06-01 19:33:20 +00:00
|
|
|
*/
|
2020-07-26 18:30:03 +00:00
|
|
|
static VOID ClearBatch(VOID)
|
2011-06-01 19:33:20 +00:00
|
|
|
{
|
2020-07-26 18:30:03 +00:00
|
|
|
TRACE("ClearBatch mem = %08x ; free = %d\n", bc->mem, bc->memfree);
|
2011-06-01 19:33:20 +00:00
|
|
|
|
2013-06-30 12:52:54 +00:00
|
|
|
if (bc->mem && bc->memfree)
|
|
|
|
cmd_free(bc->mem);
|
2011-06-01 19:33:20 +00:00
|
|
|
|
2013-06-30 12:52:54 +00:00
|
|
|
if (bc->raw_params)
|
|
|
|
cmd_free(bc->raw_params);
|
2011-06-01 19:33:20 +00:00
|
|
|
|
2013-06-30 12:52:54 +00:00
|
|
|
if (bc->params)
|
|
|
|
cmd_free(bc->params);
|
2011-06-01 19:33:20 +00:00
|
|
|
}
|
2006-02-16 23:23:37 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2017-12-03 17:49:41 +00:00
|
|
|
VOID ExitBatch(VOID)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2013-06-30 12:52:54 +00:00
|
|
|
ClearBatch();
|
2008-01-19 17:29:49 +00:00
|
|
|
|
2020-07-26 18:30:03 +00:00
|
|
|
TRACE("ExitBatch\n");
|
2008-08-21 15:33:59 +00:00
|
|
|
|
2013-06-30 12:52:54 +00:00
|
|
|
UndoRedirection(bc->RedirList, NULL);
|
|
|
|
FreeRedirection(bc->RedirList);
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2020-07-18 21:41:56 +00:00
|
|
|
#ifndef MSCMD_BATCH_ECHO
|
2013-06-30 12:52:54 +00:00
|
|
|
/* Preserve echo state across batch calls */
|
|
|
|
bEcho = bc->bEcho;
|
2020-07-18 21:41:56 +00:00
|
|
|
#endif
|
2009-03-06 18:05:45 +00:00
|
|
|
|
2013-06-30 12:52:54 +00:00
|
|
|
while (bc->setlocal)
|
|
|
|
cmd_endlocal(_T(""));
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2013-06-30 12:52:54 +00:00
|
|
|
bc = bc->prev;
|
[CMD] Fixes for Batch error execution control flow.
CORE-13713 CORE-13736
- In case execution of all batch contexts is stopped (by selecting "All"
at the Ctrl-C/Ctrl-Break prompt), notify as well the CheckCtrlBreak()
signal handler once there are no more batch contexts (this in effect
resets the internal 'bLeaveAll' static flag in CheckCtrlBreak).
This is an adaptation of the fix present in FreeCOM 1.5, first
described in https://gcfl.net/FreeDOS/command.com/bugs074g.html .
- Introduce a ParseErrorEx() helper that sets the 'bParseError' flag and
displays a customized syntax-error message, only for the first syntax
error encountered. Implement ParseError() around the *Ex function.
- In batch mode, echo the original pre-parsed batch file line if a parse
error has been encountered.
- When running a compound command - including IF, FOR, command blocks -,
and that control flow is modified by any CALL/GOTO/EXIT command,
detect this while running the compound command so as to stop it and go
back to the main batch execution loop, that will then set up the actual
new command to run.
- In GOTO, do not process any more parts of a compound command only when
we have found a valid label.
2020-05-18 00:05:53 +00:00
|
|
|
|
|
|
|
#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
|
|
|
{
|
[CMD] Fixes for Batch error execution control flow.
CORE-13713 CORE-13736
- In case execution of all batch contexts is stopped (by selecting "All"
at the Ctrl-C/Ctrl-Break prompt), notify as well the CheckCtrlBreak()
signal handler once there are no more batch contexts (this in effect
resets the internal 'bLeaveAll' static flag in CheckCtrlBreak).
This is an adaptation of the fix present in FreeCOM 1.5, first
described in https://gcfl.net/FreeDOS/command.com/bugs074g.html .
- Introduce a ParseErrorEx() helper that sets the 'bParseError' flag and
displays a customized syntax-error message, only for the first syntax
error encountered. Implement ParseError() around the *Ex function.
- In batch mode, echo the original pre-parsed batch file line if a parse
error has been encountered.
- When running a compound command - including IF, FOR, command blocks -,
and that control flow is modified by any CALL/GOTO/EXIT command,
detect this while running the compound command so as to stop it and go
back to the main batch execution loop, that will then set up the actual
new command to run.
- In GOTO, do not process any more parts of a compound command only when
we have found a valid label.
2020-05-18 00:05:53 +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;
|
2020-07-18 21:41:56 +00:00
|
|
|
|
|
|
|
#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
|
|
|
}
|
[CMD] Fixes for Batch error execution control flow.
CORE-13713 CORE-13736
- In case execution of all batch contexts is stopped (by selecting "All"
at the Ctrl-C/Ctrl-Break prompt), notify as well the CheckCtrlBreak()
signal handler once there are no more batch contexts (this in effect
resets the internal 'bLeaveAll' static flag in CheckCtrlBreak).
This is an adaptation of the fix present in FreeCOM 1.5, first
described in https://gcfl.net/FreeDOS/command.com/bugs074g.html .
- Introduce a ParseErrorEx() helper that sets the 'bParseError' flag and
displays a customized syntax-error message, only for the first syntax
error encountered. Implement ParseError() around the *Ex function.
- In batch mode, echo the original pre-parsed batch file line if a parse
error has been encountered.
- When running a compound command - including IF, FOR, command blocks -,
and that control flow is modified by any CALL/GOTO/EXIT command,
detect this while running the compound command so as to stop it and go
back to the main batch execution loop, that will then set up the actual
new command to run.
- In GOTO, do not process any more parts of a compound command only when
we have found a valid label.
2020-05-18 00:05:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Exit all the nested batch calls.
|
|
|
|
*/
|
|
|
|
VOID ExitAllBatches(VOID)
|
|
|
|
{
|
|
|
|
while (bc)
|
|
|
|
ExitBatch();
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
2011-06-01 19:33:20 +00:00
|
|
|
/*
|
2020-07-26 18:30:03 +00:00
|
|
|
* Load batch file into memory.
|
2011-06-01 19:33:20 +00:00
|
|
|
*/
|
2020-07-26 18:30:03 +00:00
|
|
|
static void BatchFile2Mem(HANDLE hBatchFile)
|
2011-06-01 19:33:20 +00:00
|
|
|
{
|
2020-07-26 18:30:03 +00:00
|
|
|
TRACE("BatchFile2Mem()\n");
|
2013-06-30 12:52:54 +00:00
|
|
|
|
|
|
|
bc->memsize = GetFileSize(hBatchFile, NULL);
|
2020-07-26 18:30:03 +00:00
|
|
|
bc->mem = (char *)cmd_alloc(bc->memsize+1); /* 1 extra for '\0' */
|
2013-06-30 12:52:54 +00:00
|
|
|
|
|
|
|
/* if memory is available, read it in and close the file */
|
2013-06-30 13:23:30 +00:00
|
|
|
if (bc->mem != NULL)
|
2013-06-30 12:52:54 +00:00
|
|
|
{
|
|
|
|
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 */
|
2013-06-30 13:23:30 +00:00
|
|
|
}
|
|
|
|
else
|
2013-06-30 12:52:54 +00:00
|
|
|
{
|
|
|
|
bc->memsize=0; /* this will prevent mem being accessed */
|
|
|
|
bc->memfree=FALSE;
|
|
|
|
}
|
|
|
|
bc->mempos = 0; /* set position to the start */
|
2011-06-01 19:33:20 +00:00
|
|
|
}
|
2006-02-16 23:23:37 +00:00
|
|
|
|
|
|
|
/*
|
2020-07-26 18:30:03 +00:00
|
|
|
* Start batch file execution.
|
2006-02-16 23:23:37 +00:00
|
|
|
*
|
|
|
|
* The firstword parameter is the full filename of the batch file.
|
|
|
|
*/
|
2017-12-03 17:49:41 +00:00
|
|
|
INT Batch(LPTSTR fullname, LPTSTR firstword, LPTSTR param, PARSED_COMMAND *Cmd)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2013-06-30 12:52:54 +00:00
|
|
|
INT ret = 0;
|
2020-07-26 18:30:03 +00:00
|
|
|
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;
|
2020-07-26 18:30:03 +00:00
|
|
|
BATCH_CONTEXT new;
|
|
|
|
PFOR_CONTEXT saved_fc;
|
2013-06-30 12:52:54 +00:00
|
|
|
|
|
|
|
SetLastError(0);
|
2013-06-30 13:23:30 +00:00
|
|
|
if (bc && bc->mem)
|
2013-06-30 12:52:54 +00:00
|
|
|
{
|
|
|
|
TCHAR fpname[MAX_PATH];
|
2020-07-26 18:30:03 +00:00
|
|
|
GetFullPathName(fullname, ARRAYSIZE(fpname), fpname, NULL);
|
|
|
|
if (_tcsicmp(bc->BatchFilePath, fpname) == 0)
|
|
|
|
bSameFn = TRUE;
|
2013-06-30 12:52:54 +00:00
|
|
|
}
|
2020-07-26 18:30:03 +00:00
|
|
|
TRACE("Batch(\'%s\', \'%s\', \'%s\') bSameFn = %d\n",
|
|
|
|
debugstr_aw(fullname), debugstr_aw(firstword), debugstr_aw(param), bSameFn);
|
2013-06-30 12:52:54 +00:00
|
|
|
|
2020-07-26 18:30:03 +00:00
|
|
|
if (!bSameFn)
|
2013-06-30 12:52:54 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
|
2024-07-16 23:34:15 +00:00
|
|
|
if (bc && Cmd == bc->current)
|
2013-06-30 12:52:54 +00:00
|
|
|
{
|
|
|
|
/* Then we are transferring to another batch */
|
2024-07-16 23:34:15 +00:00
|
|
|
if (!bSameFn)
|
|
|
|
ClearBatch();
|
2013-06-30 12:52:54 +00:00
|
|
|
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 */
|
2020-07-26 18:30:03 +00:00
|
|
|
if (bSameFn)
|
2017-11-18 22:41:31 +00:00
|
|
|
{
|
2013-06-30 12:52:54 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-07-26 18:30:03 +00:00
|
|
|
GetFullPathName(fullname, ARRAYSIZE(bc->BatchFilePath), bc->BatchFilePath, NULL);
|
|
|
|
|
|
|
|
/* If a new batch file, load it into memory and close the file */
|
|
|
|
if (!bSameFn)
|
2013-06-30 12:52:54 +00:00
|
|
|
{
|
|
|
|
BatchFile2Mem(hFile);
|
|
|
|
CloseHandle(hFile);
|
|
|
|
}
|
|
|
|
|
2020-07-26 18:30:03 +00:00
|
|
|
bc->mempos = 0; /* Go to the beginning of the batch file */
|
2020-07-18 21:41:56 +00:00
|
|
|
#ifndef MSCMD_BATCH_ECHO
|
2013-06-30 12:52:54 +00:00
|
|
|
bc->bEcho = bEcho; /* Preserve echo across batch calls */
|
2020-07-18 21:41:56 +00:00
|
|
|
#endif
|
2013-06-30 12:52:54 +00:00
|
|
|
for (i = 0; i < 10; i++)
|
|
|
|
bc->shiftlevel[i] = i;
|
|
|
|
|
2020-08-09 19:13:13 +00:00
|
|
|
/* Parse the batch parameters */
|
|
|
|
if (!BatchParams(firstword, param, &bc->raw_params, &bc->params))
|
2009-04-12 23:51:15 +00:00
|
|
|
return 1;
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2013-06-30 12:52:54 +00:00
|
|
|
/* 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)
|
|
|
|
{
|
2024-07-16 23:34:15 +00:00
|
|
|
/* Default the top-level batch context type
|
|
|
|
* to .BAT, unless this is a .CMD file */
|
|
|
|
PTCHAR dotext = _tcsrchr(bc->BatchFilePath, _T('.'));
|
|
|
|
BatType = (dotext && (!_tcsicmp(dotext, _T(".cmd")))) ? CMD_TYPE : BAT_TYPE;
|
2020-07-18 21:41:56 +00:00
|
|
|
|
|
|
|
#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
|
|
|
}
|
|
|
|
|
[CMD] CALL: Fix the implementation of the CALL command, make it more compatible with Windows' CMD.
- Fail if no parameter is provided.
- The "CALL :label args..." syntax is available only when command extensions
are enabled. Fail if this syntax is used outside of a batch context.
- Reparse the CALL command parameter with the command parser, in order
to accurately parse and interpret it as a possible command (including
escape carets, etc...) and not duplicate the logic.
** CURRENT Windows' CMD-compatibility LIMITATION ** (may be lifted in
a "ROS-specific" running mode of CMD): only allow standard commands to
be specified as parameter of the CALL command.
This reparsing behaviour can be observed in Windows' CMD, by dumping
the interpreted commands after enabling the cmd!fDumpParse flag from
a debugger (using public symbols).
- When reparsing, we should tell the parser to NOT ignore lines that
start with a colon, because in this situation these are to be
considered as valid "commands" (for parsing "CALL :label").
* For Windows' CMD-compatibility, the remaining escape carets need to
be doubled again so that, after the new parser step, they are escaped
back to their original form. But then we also need to do it the "buggy"
way à la Windows, where carets in quotes are doubled either! However
when being re-parsed, since they are in quotes they remain doubled!!
(see "Phase 6" in https://stackoverflow.com/a/4095133/13530036 ).
* A MSCMD_CALL_QUIRKS define allows to disable this buggy behaviour,
and instead tell the parser to not not interpret the escape carets.
- When initializing a new batch context when the "CALL :label" syntax is
used, ensure that we reuse the same batch file position pointer as its
parent, so as to have correct call label ordering behaviour.
That is,
:label
ECHO hi
CALL :label
:label
ECHO bye
should display:
hi
bye
bye
i.e., the CALL calls the second label instead of the first one (and
thus entering into an infinite loop).
Finally, the "CALL :label" syntax strips the first ':' away, so, as a
side-effect, the command "CALL :EOF" fails (otherwise it would perform
a "GOTO :EOF" and succeeds), while "CALL ::EOF" succeeds.
Fixes some cmd_winetests.
2020-07-26 18:30:21 +00:00
|
|
|
/* If this is a "CALL :label args ...", call a subroutine of
|
|
|
|
* the current batch file, only if extensions are enabled. */
|
|
|
|
if (bEnableExtensions && (*firstword == _T(':')))
|
|
|
|
{
|
|
|
|
LPTSTR expLabel;
|
|
|
|
|
|
|
|
/* Position at the place of the parent file (which is the same as the caller) */
|
|
|
|
bc->mempos = (bc->prev ? bc->prev->mempos : 0);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Jump to the label. Strip the label's colon; as a side-effect
|
|
|
|
* this will forbid "CALL :EOF"; however "CALL ::EOF" will work!
|
|
|
|
*/
|
|
|
|
bc->current = Cmd;
|
|
|
|
++firstword;
|
|
|
|
|
|
|
|
/* Expand the label only! (simulate a GOTO command as in Windows' CMD) */
|
|
|
|
expLabel = DoDelayedExpansion(firstword);
|
|
|
|
ret = cmd_goto(expLabel ? expLabel : firstword);
|
|
|
|
if (expLabel)
|
|
|
|
cmd_free(expLabel);
|
|
|
|
}
|
[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
|
|
|
|
2013-06-30 12:52:54 +00:00
|
|
|
/* 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)
|
[CMD] Fixes for Batch error execution control flow.
CORE-13713 CORE-13736
- In case execution of all batch contexts is stopped (by selecting "All"
at the Ctrl-C/Ctrl-Break prompt), notify as well the CheckCtrlBreak()
signal handler once there are no more batch contexts (this in effect
resets the internal 'bLeaveAll' static flag in CheckCtrlBreak).
This is an adaptation of the fix present in FreeCOM 1.5, first
described in https://gcfl.net/FreeDOS/command.com/bugs074g.html .
- Introduce a ParseErrorEx() helper that sets the 'bParseError' flag and
displays a customized syntax-error message, only for the first syntax
error encountered. Implement ParseError() around the *Ex function.
- In batch mode, echo the original pre-parsed batch file line if a parse
error has been encountered.
- When running a compound command - including IF, FOR, command blocks -,
and that control flow is modified by any CALL/GOTO/EXIT command,
detect this while running the compound command so as to stop it and go
back to the main batch execution loop, that will then set up the actual
new command to run.
- In GOTO, do not process any more parts of a compound command only when
we have found a valid label.
2020-05-18 00:05:53 +00:00
|
|
|
{
|
|
|
|
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();
|
2020-07-29 23:44:43 +00:00
|
|
|
ret = 1;
|
[CMD] Fixes for Batch error execution control flow.
CORE-13713 CORE-13736
- In case execution of all batch contexts is stopped (by selecting "All"
at the Ctrl-C/Ctrl-Break prompt), notify as well the CheckCtrlBreak()
signal handler once there are no more batch contexts (this in effect
resets the internal 'bLeaveAll' static flag in CheckCtrlBreak).
This is an adaptation of the fix present in FreeCOM 1.5, first
described in https://gcfl.net/FreeDOS/command.com/bugs074g.html .
- Introduce a ParseErrorEx() helper that sets the 'bParseError' flag and
displays a customized syntax-error message, only for the first syntax
error encountered. Implement ParseError() around the *Ex function.
- In batch mode, echo the original pre-parsed batch file line if a parse
error has been encountered.
- When running a compound command - including IF, FOR, command blocks -,
and that control flow is modified by any CALL/GOTO/EXIT command,
detect this while running the compound command so as to stop it and go
back to the main batch execution loop, that will then set up the actual
new command to run.
- In GOTO, do not process any more parts of a compound command only when
we have found a valid label.
2020-05-18 00:05:53 +00:00
|
|
|
break;
|
|
|
|
}
|
2013-06-30 12:52:54 +00:00
|
|
|
|
|
|
|
/* JPP 19980807 */
|
2020-05-18 00:03:15 +00:00
|
|
|
/* Echo the command and execute it */
|
2013-06-30 12:52:54 +00:00
|
|
|
bc->current = Cmd;
|
2020-05-18 00:03:15 +00:00
|
|
|
ret = ExecuteCommandWithEcho(Cmd);
|
2013-06-30 12:52:54 +00:00
|
|
|
FreeCommand(Cmd);
|
|
|
|
}
|
2020-09-27 18:24:38 +00:00
|
|
|
if (bExit)
|
|
|
|
{
|
|
|
|
/* Stop all execution */
|
|
|
|
ExitAllBatches();
|
|
|
|
}
|
2013-06-30 12:52:54 +00:00
|
|
|
|
[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;
|
2020-07-18 21:41:56 +00:00
|
|
|
|
|
|
|
#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
|
|
|
}
|
|
|
|
|
2020-07-29 23:44:43 +00:00
|
|
|
/* Restore the FOR variables */
|
2013-06-30 12:52:54 +00:00
|
|
|
fc = saved_fc;
|
2020-07-29 23:44:43 +00:00
|
|
|
|
|
|
|
/* Always return the last command's return code */
|
|
|
|
TRACE("Batch: returns %d\n", ret);
|
2013-06-30 12:52:54 +00:00
|
|
|
return ret;
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
2008-08-21 15:33:59 +00:00
|
|
|
VOID AddBatchRedirection(REDIRECTION **RedirList)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2013-06-30 12:52:54 +00:00
|
|
|
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;
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
2011-06-01 19:33:20 +00:00
|
|
|
/*
|
|
|
|
* Read a single line from the batch file from the current batch/memory position.
|
|
|
|
* Almost a copy of FileGetString with same UNICODE handling
|
|
|
|
*/
|
2017-12-03 17:49:41 +00:00
|
|
|
BOOL BatchGetString(LPTSTR lpBuffer, INT nBufferLength)
|
2011-06-01 19:33:20 +00:00
|
|
|
{
|
2013-06-30 12:52:54 +00:00
|
|
|
INT len = 0;
|
2021-06-23 14:26:50 +00:00
|
|
|
|
2013-06-30 12:52:54 +00:00
|
|
|
/* read all chars from memory until a '\n' is encountered */
|
2013-06-30 13:23:30 +00:00
|
|
|
if (bc->mem)
|
2013-06-30 12:52:54 +00:00
|
|
|
{
|
2021-06-23 14:26:50 +00:00
|
|
|
for (; ((bc->mempos + len) < bc->memsize && len < (nBufferLength-1)); len++)
|
|
|
|
{
|
|
|
|
#ifndef _UNICODE
|
|
|
|
lpBuffer[len] = bc->mem[bc->mempos + len];
|
|
|
|
#endif
|
|
|
|
if (bc->mem[bc->mempos + len] == '\n')
|
2013-06-30 12:52:54 +00:00
|
|
|
{
|
|
|
|
len++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2011-06-01 19:33:20 +00:00
|
|
|
#ifdef _UNICODE
|
2021-06-23 14:26:50 +00:00
|
|
|
nBufferLength = MultiByteToWideChar(OutputCodePage, 0, &bc->mem[bc->mempos], len, lpBuffer, nBufferLength);
|
|
|
|
lpBuffer[nBufferLength] = L'\0';
|
|
|
|
lpBuffer[len] = '\0';
|
2011-06-01 19:33:20 +00:00
|
|
|
#endif
|
2021-06-23 14:26:50 +00:00
|
|
|
bc->mempos += len;
|
2013-06-30 12:52:54 +00:00
|
|
|
}
|
2011-06-01 19:33:20 +00:00
|
|
|
|
2021-06-23 14:26:50 +00:00
|
|
|
return len != 0;
|
2011-06-01 19:33:20 +00:00
|
|
|
}
|
|
|
|
|
2006-02-16 23:23:37 +00:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*/
|
2017-12-03 17:49:41 +00:00
|
|
|
LPTSTR ReadBatchLine(VOID)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2020-07-26 18:30:03 +00:00
|
|
|
TRACE("ReadBatchLine()\n");
|
2013-06-30 12:52:54 +00:00
|
|
|
|
|
|
|
/* User halt */
|
2020-07-26 18:30:03 +00:00
|
|
|
if (CheckCtrlBreak(BREAK_BATCHFILE))
|
2013-06-30 12:52:54 +00:00
|
|
|
{
|
[CMD] Fixes for Batch error execution control flow.
CORE-13713 CORE-13736
- In case execution of all batch contexts is stopped (by selecting "All"
at the Ctrl-C/Ctrl-Break prompt), notify as well the CheckCtrlBreak()
signal handler once there are no more batch contexts (this in effect
resets the internal 'bLeaveAll' static flag in CheckCtrlBreak).
This is an adaptation of the fix present in FreeCOM 1.5, first
described in https://gcfl.net/FreeDOS/command.com/bugs074g.html .
- Introduce a ParseErrorEx() helper that sets the 'bParseError' flag and
displays a customized syntax-error message, only for the first syntax
error encountered. Implement ParseError() around the *Ex function.
- In batch mode, echo the original pre-parsed batch file line if a parse
error has been encountered.
- When running a compound command - including IF, FOR, command blocks -,
and that control flow is modified by any CALL/GOTO/EXIT command,
detect this while running the compound command so as to stop it and go
back to the main batch execution loop, that will then set up the actual
new command to run.
- In GOTO, do not process any more parts of a compound command only when
we have found a valid label.
2020-05-18 00:05:53 +00:00
|
|
|
ExitAllBatches();
|
2013-06-30 12:52:54 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2020-07-26 18:30:03 +00:00
|
|
|
if (!BatchGetString(textline, ARRAYSIZE(textline) - 1))
|
2013-06-30 12:52:54 +00:00
|
|
|
{
|
2020-07-26 18:30:03 +00:00
|
|
|
TRACE("ReadBatchLine(): Reached EOF!\n");
|
|
|
|
/* End of file */
|
2013-06-30 12:52:54 +00:00
|
|
|
ExitBatch();
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2020-07-26 18:30:03 +00:00
|
|
|
TRACE("ReadBatchLine(): textline: \'%s\'\n", debugstr_aw(textline));
|
2013-06-30 12:52:54 +00:00
|
|
|
|
[CMD] CALL: Fix the implementation of the CALL command, make it more compatible with Windows' CMD.
- Fail if no parameter is provided.
- The "CALL :label args..." syntax is available only when command extensions
are enabled. Fail if this syntax is used outside of a batch context.
- Reparse the CALL command parameter with the command parser, in order
to accurately parse and interpret it as a possible command (including
escape carets, etc...) and not duplicate the logic.
** CURRENT Windows' CMD-compatibility LIMITATION ** (may be lifted in
a "ROS-specific" running mode of CMD): only allow standard commands to
be specified as parameter of the CALL command.
This reparsing behaviour can be observed in Windows' CMD, by dumping
the interpreted commands after enabling the cmd!fDumpParse flag from
a debugger (using public symbols).
- When reparsing, we should tell the parser to NOT ignore lines that
start with a colon, because in this situation these are to be
considered as valid "commands" (for parsing "CALL :label").
* For Windows' CMD-compatibility, the remaining escape carets need to
be doubled again so that, after the new parser step, they are escaped
back to their original form. But then we also need to do it the "buggy"
way à la Windows, where carets in quotes are doubled either! However
when being re-parsed, since they are in quotes they remain doubled!!
(see "Phase 6" in https://stackoverflow.com/a/4095133/13530036 ).
* A MSCMD_CALL_QUIRKS define allows to disable this buggy behaviour,
and instead tell the parser to not not interpret the escape carets.
- When initializing a new batch context when the "CALL :label" syntax is
used, ensure that we reuse the same batch file position pointer as its
parent, so as to have correct call label ordering behaviour.
That is,
:label
ECHO hi
CALL :label
:label
ECHO bye
should display:
hi
bye
bye
i.e., the CALL calls the second label instead of the first one (and
thus entering into an infinite loop).
Finally, the "CALL :label" syntax strips the first ':' away, so, as a
side-effect, the command "CALL :EOF" fails (otherwise it would perform
a "GOTO :EOF" and succeeds), while "CALL ::EOF" succeeds.
Fixes some cmd_winetests.
2020-07-26 18:30:21 +00:00
|
|
|
#if 1
|
|
|
|
//
|
|
|
|
// FIXME: This is redundant, but keep it for the moment until we correctly
|
|
|
|
// hande the end-of-file situation here, in ReadLine() and in the parser.
|
|
|
|
// (In an EOF, the previous BatchGetString() call will return FALSE but
|
|
|
|
// we want not to run the ExitBatch() at first, but wait later to do it.)
|
|
|
|
//
|
2013-06-30 12:52:54 +00:00
|
|
|
if (textline[_tcslen(textline) - 1] != _T('\n'))
|
|
|
|
_tcscat(textline, _T("\n"));
|
[CMD] CALL: Fix the implementation of the CALL command, make it more compatible with Windows' CMD.
- Fail if no parameter is provided.
- The "CALL :label args..." syntax is available only when command extensions
are enabled. Fail if this syntax is used outside of a batch context.
- Reparse the CALL command parameter with the command parser, in order
to accurately parse and interpret it as a possible command (including
escape carets, etc...) and not duplicate the logic.
** CURRENT Windows' CMD-compatibility LIMITATION ** (may be lifted in
a "ROS-specific" running mode of CMD): only allow standard commands to
be specified as parameter of the CALL command.
This reparsing behaviour can be observed in Windows' CMD, by dumping
the interpreted commands after enabling the cmd!fDumpParse flag from
a debugger (using public symbols).
- When reparsing, we should tell the parser to NOT ignore lines that
start with a colon, because in this situation these are to be
considered as valid "commands" (for parsing "CALL :label").
* For Windows' CMD-compatibility, the remaining escape carets need to
be doubled again so that, after the new parser step, they are escaped
back to their original form. But then we also need to do it the "buggy"
way à la Windows, where carets in quotes are doubled either! However
when being re-parsed, since they are in quotes they remain doubled!!
(see "Phase 6" in https://stackoverflow.com/a/4095133/13530036 ).
* A MSCMD_CALL_QUIRKS define allows to disable this buggy behaviour,
and instead tell the parser to not not interpret the escape carets.
- When initializing a new batch context when the "CALL :label" syntax is
used, ensure that we reuse the same batch file position pointer as its
parent, so as to have correct call label ordering behaviour.
That is,
:label
ECHO hi
CALL :label
:label
ECHO bye
should display:
hi
bye
bye
i.e., the CALL calls the second label instead of the first one (and
thus entering into an infinite loop).
Finally, the "CALL :label" syntax strips the first ':' away, so, as a
side-effect, the command "CALL :EOF" fails (otherwise it would perform
a "GOTO :EOF" and succeeds), while "CALL ::EOF" succeeds.
Fixes some cmd_winetests.
2020-07-26 18:30:21 +00:00
|
|
|
#endif
|
2013-06-30 12:52:54 +00:00
|
|
|
|
|
|
|
return textline;
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* EOF */
|