[CMD] Change ERRORLEVEL behaviour for commands ASSOC, PATH, PROMPT and SET.
authorHermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
Wed, 1 Jul 2020 00:15:52 +0000 (02:15 +0200)
committerHermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
Tue, 22 Sep 2020 22:22:46 +0000 (00:22 +0200)
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).

base/shell/cmd/assoc.c
base/shell/cmd/batch.c
base/shell/cmd/batch.h
base/shell/cmd/path.c
base/shell/cmd/prompt.c
base/shell/cmd/set.c

index a9ce5c6..3e5611e 100644 (file)
@@ -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 */
index e9a1e5a..5015bd3 100644 (file)
@@ -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;
 
index d40b640..25b8857 100644 (file)
@@ -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;
 
index 6235b45..2286a13 100644 (file)
 
 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
index ce47cd7..d835ea5 100644 (file)
@@ -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
 
index 0128314..beded95 100644 (file)
@@ -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