https://git.reactos.org/?p=reactos.git;a=commitdiff;h=63316df5202f6f038cc47…
commit 63316df5202f6f038cc47841ed81544fc18263e0
Author: Hermès Bélusca-Maïto <hermes.belusca-maito(a)reactos.org>
AuthorDate: Wed Jul 1 02:15:52 2020 +0200
Commit: Hermès Bélusca-Maïto <hermes.belusca-maito(a)reactos.org>
CommitDate: Wed Sep 23 00:22:46 2020 +0200
[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.ad…
(comment by Mark Zbikowski).
---
base/shell/cmd/assoc.c | 33 +++++++++++++++++++++++++--------
base/shell/cmd/batch.c | 43 ++++++++++++++++++++++++++++++++++++++-----
base/shell/cmd/batch.h | 16 ++++++++++++++++
base/shell/cmd/path.c | 34 +++++++++++++++++++++++++---------
base/shell/cmd/prompt.c | 17 ++++++++++++++---
base/shell/cmd/set.c | 49 ++++++++++++++++++++++++++++++++++---------------
6 files changed, 152 insertions(+), 40 deletions(-)
diff --git a/base/shell/cmd/assoc.c b/base/shell/cmd/assoc.c
index a9ce5c6682a..3e5611e6b1f 100644
--- a/base/shell/cmd/assoc.c
+++ b/base/shell/cmd/assoc.c
@@ -192,6 +192,7 @@ RemoveAssociation(
INT CommandAssoc(LPTSTR param)
{
+ INT retval = 0;
LPTSTR lpEqualSign;
/* Print help */
@@ -201,12 +202,10 @@ INT CommandAssoc(LPTSTR param)
return 0;
}
- nErrorLevel = 0;
-
if (_tcslen(param) == 0)
{
PrintAllAssociations();
- return 0;
+ goto Quit;
}
lpEqualSign = _tcschr(param, _T('='));
@@ -218,7 +217,8 @@ INT CommandAssoc(LPTSTR param)
{
WARN("Cannot allocate memory for extension!\n");
error_out_of_memory();
- return 1;
+ retval = 1;
+ goto Quit;
}
_tcsncpy(extension, param, lpEqualSign - param);
@@ -228,26 +228,43 @@ INT CommandAssoc(LPTSTR param)
* in the string, then delete the key. */
if (_tcslen(fileType) == 0)
{
- RemoveAssociation(extension);
+ retval = RemoveAssociation(extension);
}
else
/* Otherwise, add the key and print out the association */
{
- AddAssociation(extension, fileType);
+ retval = AddAssociation(extension, fileType);
PrintAssociation(extension);
}
cmd_free(extension);
+
+ if (retval)
+ retval = 1; /* Fixup the error value */
}
else
{
/* No equal sign, print all associations */
- INT retval = PrintAssociation(param);
+ retval = PrintAssociation(param);
if (retval == 0) /* If nothing printed out */
+ {
ConOutResPrintf(STRING_ASSOC_ERROR, param);
+ retval = 1; /* Fixup the error value */
+ }
}
- return 0;
+Quit:
+ if (BatType != CMD_TYPE)
+ {
+ if (retval != 0)
+ nErrorLevel = retval;
+ }
+ else
+ {
+ nErrorLevel = retval;
+ }
+
+ return retval;
}
#endif /* INCLUDE_CMD_ASSOC */
diff --git a/base/shell/cmd/batch.c b/base/shell/cmd/batch.c
index e9a1e5aa76e..5015bd338ea 100644
--- a/base/shell/cmd/batch.c
+++ b/base/shell/cmd/batch.c
@@ -63,6 +63,7 @@
/* The stack of current batch contexts.
* NULL when no batch is active.
*/
+BATCH_TYPE BatType = NONE;
PBATCH_CONTEXT bc = NULL;
BOOL bEcho = TRUE; /* The echo flag */
@@ -209,7 +210,10 @@ VOID ExitBatch(VOID)
/* If there is no more batch contexts, notify the signal handler */
if (!bc)
+ {
CheckCtrlBreak(BREAK_OUTOFBATCH);
+ BatType = NONE;
+ }
}
/*
@@ -258,7 +262,8 @@ INT Batch(LPTSTR fullname, LPTSTR firstword, LPTSTR param,
PARSED_COMMAND *Cmd)
INT ret = 0;
INT i;
HANDLE hFile = NULL;
- BOOL bSameFn = FALSE;
+ BOOLEAN bSameFn = FALSE;
+ BOOLEAN bTopLevel;
BATCH_CONTEXT new;
PFOR_CONTEXT saved_fc;
@@ -286,6 +291,13 @@ INT Batch(LPTSTR fullname, LPTSTR firstword, LPTSTR param,
PARSED_COMMAND *Cmd)
}
}
+ /*
+ * 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 */
@@ -353,14 +365,28 @@ INT Batch(LPTSTR fullname, LPTSTR firstword, LPTSTR param,
PARSED_COMMAND *Cmd)
return 1;
}
- /* Check if this is a "CALL :label" */
- if (*firstword == _T(':'))
- ret = cmd_goto(firstword);
-
/* If we are calling from inside a FOR, hide the FOR variables */
saved_fc = fc;
fc = NULL;
+ /* 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;
+ }
+ }
+
+ /* 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)
@@ -393,6 +419,13 @@ INT Batch(LPTSTR fullname, LPTSTR firstword, LPTSTR param,
PARSED_COMMAND *Cmd)
FreeCommand(Cmd);
}
+ /* Perform top-level batch cleanup */
+ if (!bc || bTopLevel)
+ {
+ /* Reset the top-level batch context type */
+ BatType = NONE;
+ }
+
/* Restore the FOR variables */
fc = saved_fc;
diff --git a/base/shell/cmd/batch.h b/base/shell/cmd/batch.h
index d40b640e421..25b8857b1c3 100644
--- a/base/shell/cmd/batch.h
+++ b/base/shell/cmd/batch.h
@@ -4,6 +4,21 @@
#pragma once
+/*
+ * This batch type enumeration allows us to adjust the behaviour of some commands
+ * depending on whether they are run from within a .BAT or a .CMD file.
+ * The behaviour is selected when the top-level batch file is loaded,
+ * and it remains the same for any child batch file that may be loaded later.
+ *
+ * See
https://ss64.com/nt/errorlevel.html for more details.
+ */
+typedef enum _BATCH_TYPE
+{
+ NONE,
+ BAT_TYPE, /* Old-style DOS batch file */
+ CMD_TYPE /* New-style NT OS/2 batch file */
+} BATCH_TYPE;
+
typedef struct _BATCH_CONTEXT
{
struct _BATCH_CONTEXT *prev;
@@ -34,6 +49,7 @@ typedef struct _FOR_CONTEXT
* The stack of current batch contexts.
* NULL when no batch is active.
*/
+extern BATCH_TYPE BatType;
extern PBATCH_CONTEXT bc;
extern PFOR_CONTEXT fc;
diff --git a/base/shell/cmd/path.c b/base/shell/cmd/path.c
index 6235b459362..2286a13e674 100644
--- a/base/shell/cmd/path.c
+++ b/base/shell/cmd/path.c
@@ -35,14 +35,14 @@
INT cmd_path(LPTSTR param)
{
+ INT retval = 0;
+
if (!_tcsncmp(param, _T("/?"), 2))
{
ConOutResPaging(TRUE, STRING_PATH_HELP1);
return 0;
}
- nErrorLevel = 0;
-
/* If param is empty, display the PATH environment variable */
if (!param || !*param)
{
@@ -53,7 +53,9 @@ INT cmd_path(LPTSTR param)
if (!pszBuffer)
{
WARN("Cannot allocate memory for pszBuffer!\n");
- return 1;
+ error_out_of_memory();
+ retval = 1;
+ goto Quit;
}
dwBuffer = GetEnvironmentVariable(_T("PATH"), pszBuffer,
ENV_BUFFER_SIZE);
@@ -61,7 +63,8 @@ INT cmd_path(LPTSTR param)
{
cmd_free(pszBuffer);
ConErrResPrintf(STRING_SET_ENV_ERROR, _T("PATH"));
- return 0;
+ retval = 0;
+ goto Quit;
}
else if (dwBuffer > ENV_BUFFER_SIZE)
{
@@ -70,8 +73,10 @@ INT cmd_path(LPTSTR param)
if (!pszBuffer)
{
WARN("Cannot reallocate memory for pszBuffer!\n");
+ error_out_of_memory();
cmd_free(pszOldBuffer);
- return 1;
+ retval = 1;
+ goto Quit;
}
GetEnvironmentVariable(_T("PATH"), pszBuffer, dwBuffer);
}
@@ -79,7 +84,8 @@ INT cmd_path(LPTSTR param)
ConOutPrintf(_T("PATH=%s\n"), pszBuffer);
cmd_free(pszBuffer);
- return 0;
+ retval = 0;
+ goto Quit;
}
/* Skip leading '=' */
@@ -89,11 +95,21 @@ INT cmd_path(LPTSTR param)
/* Set PATH environment variable */
if (!SetEnvironmentVariable(_T("PATH"), param))
{
- nErrorLevel = 1;
- return 1;
+ retval = 1;
+ }
+
+Quit:
+ if (BatType != CMD_TYPE)
+ {
+ if (retval != 0)
+ nErrorLevel = retval;
+ }
+ else
+ {
+ nErrorLevel = retval;
}
- return 0;
+ return retval;
}
#endif
diff --git a/base/shell/cmd/prompt.c b/base/shell/cmd/prompt.c
index ce47cd70499..d835ea5f1e3 100644
--- a/base/shell/cmd/prompt.c
+++ b/base/shell/cmd/prompt.c
@@ -243,10 +243,11 @@ VOID PrintPrompt(VOID)
INT cmd_prompt(LPTSTR param)
{
+ INT retval = 0;
+
if (!_tcsncmp(param, _T("/?"), 2))
{
ConOutResPaging(TRUE, STRING_PROMPT_HELP1);
-
#ifdef FEATURE_DIRECTORY_STACK
ConOutResPaging(FALSE, STRING_PROMPT_HELP2);
#endif
@@ -263,10 +264,20 @@ INT cmd_prompt(LPTSTR param)
if (!SetEnvironmentVariable(_T("PROMPT"),
(param && param[0] != _T('\0') ? param :
NULL)))
{
- return 1;
+ retval = 1;
+ }
+
+ if (BatType != CMD_TYPE)
+ {
+ if (retval != 0)
+ nErrorLevel = retval;
+ }
+ else
+ {
+ nErrorLevel = retval;
}
- return 0;
+ return retval;
}
#endif
diff --git a/base/shell/cmd/set.c b/base/shell/cmd/set.c
index 0128314ca57..beded9515fc 100644
--- a/base/shell/cmd/set.c
+++ b/base/shell/cmd/set.c
@@ -71,6 +71,7 @@ GetQuotedString(TCHAR *p)
INT cmd_set(LPTSTR param)
{
+ INT retval = 0;
LPTSTR p;
LPTSTR lpEnv;
LPTSTR lpOutput;
@@ -103,7 +104,8 @@ INT cmd_set(LPTSTR param)
FreeEnvironmentStrings(lpEnv);
}
- return 0;
+ retval = 0;
+ goto Quit;
}
/* The /A does *NOT* have to be followed by a whitespace */
@@ -115,9 +117,14 @@ INT cmd_set(LPTSTR param)
if (!Success)
{
/* Might seem random but this is what windows xp does -- This is a message ID
*/
- nErrorLevel = 9165;
+ retval = 9165;
+ }
+ // return !Success;
+ else
+ {
+ retval = 0;
}
- return !Success;
+ goto Quit;
}
if (!_tcsnicmp(param, _T("/P"), 2))
@@ -128,8 +135,8 @@ INT cmd_set(LPTSTR param)
if (!p)
{
ConErrResPuts(STRING_SYNTAX_COMMAND_INCORRECT);
- nErrorLevel = 1;
- return 1;
+ retval = 1;
+ goto Quit;
}
*p++ = _T('\0');
@@ -138,10 +145,11 @@ INT cmd_set(LPTSTR param)
if (!*value || !SetEnvironmentVariable(param, value))
{
- nErrorLevel = 1;
- return 1;
+ retval = 1;
+ goto Quit;
}
- return 0;
+ retval = 0;
+ goto Quit;
}
param = GetQuotedString(param);
@@ -154,15 +162,15 @@ INT cmd_set(LPTSTR param)
{
/* Handle set =val case */
ConErrResPuts(STRING_SYNTAX_COMMAND_INCORRECT);
- nErrorLevel = 1;
- return 1;
+ retval = 1;
+ goto Quit;
}
*p++ = _T('\0');
if (!SetEnvironmentVariable(param, *p ? p : NULL))
{
- nErrorLevel = 1;
- return 1;
+ retval = 1;
+ goto Quit;
}
}
else
@@ -220,12 +228,23 @@ INT cmd_set(LPTSTR param)
if (!bFound)
{
ConErrResPrintf(STRING_SET_ENV_ERROR, param);
- nErrorLevel = 1;
- return 1;
+ retval = 1;
+ goto Quit;
}
}
- return 0;
+Quit:
+ if (BatType != CMD_TYPE)
+ {
+ if (retval != 0)
+ nErrorLevel = retval;
+ }
+ else
+ {
+ nErrorLevel = retval;
+ }
+
+ return retval;
}
static INT