cmd: Command chain fixes

Flávio J. Saraiva flaviojs2005 at gmail.com
Sun Nov 6 14:47:41 CST 2016


Testing shows that '&&' precedes '||' and '||' precedes '&', so
'a && b || c & b' is equivalent to '(((a && b) || c) & d)'

This commit fixes chains that begin at the start of the line and IF/FOR
command chains that start with a '('. To fix the other IF/FOR chains we
need to make non-trivial changes during the parsing stage, so the fix is
not part this commit.

Sample error cases:
  'if 1==1 echo1||echo2'
  'for %i in (1) do echo 1||echo 2'

Make TYPE also set errorlevel 1 with no arguments.
Make CALL chain when errorlevel is 0.
Other commands either always chain, which is the default behavior of
this code, or are not being tested.

Based on how cmd.exe works in Windows XP.

Signed-off-by: Flávio J. Saraiva <flaviojs2005 at gmail.com>
---
 programs/cmd/batch.c                     |  5 ++-
 programs/cmd/builtins.c                  |  8 +++-
 programs/cmd/tests/test_builtins.cmd     | 30 +++++++++++++++
 programs/cmd/tests/test_builtins.cmd.exp | 55 +++++++++++++++++++++++++++-
 programs/cmd/wcmd.h                      | 15 ++++----
 programs/cmd/wcmdmain.c                  | 63 ++++++++++++++++++++++++++------
 6 files changed, 154 insertions(+), 22 deletions(-)

diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c
index 1a78b55..2e0faef 100644
--- a/programs/cmd/batch.c
+++ b/programs/cmd/batch.c
@@ -691,7 +691,7 @@ void WCMD_HandleTildaModifiers(WCHAR **start, BOOL atExecute)
  *	If there is a leading ':', calls within this batch program
  *	otherwise launches another program.
  */
-void WCMD_call (WCHAR *command) {
+BOOL WCMD_call (WCHAR *command) {
 
   /* Run other program if no leading ':' */
   if (*command != ':') {
@@ -729,4 +729,7 @@ void WCMD_call (WCHAR *command) {
       WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_CALLINSCRIPT));
     }
   }
+
+  /* Can only chain with errorlevel 0 */
+  return(errorlevel == 0);
 }
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c
index a29a502..10060fe 100644
--- a/programs/cmd/builtins.c
+++ b/programs/cmd/builtins.c
@@ -4324,7 +4324,7 @@ void WCMD_title (const WCHAR *args) {
  * Copy a file to standard output.
  */
 
-void WCMD_type (WCHAR *args) {
+BOOL WCMD_type (WCHAR *args) {
 
   int   argno         = 0;
   WCHAR *argN          = args;
@@ -4332,7 +4332,8 @@ void WCMD_type (WCHAR *args) {
 
   if (param1[0] == 0x00) {
     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
-    return;
+    errorlevel = 1;
+    return FALSE;
   }
 
   if (param2[0] != 0x00) writeHeaders = TRUE;
@@ -4355,6 +4356,7 @@ void WCMD_type (WCHAR *args) {
       WCMD_print_error ();
       WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
       errorlevel = 1;
+      return FALSE;
     } else {
       if (writeHeaders) {
         static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
@@ -4368,6 +4370,8 @@ void WCMD_type (WCHAR *args) {
       CloseHandle (h);
     }
   }
+
+  return TRUE;
 }
 
 /****************************************************************************
diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd
index 38a7700..e0014e1 100644
--- a/programs/cmd/tests/test_builtins.cmd
+++ b/programs/cmd/tests/test_builtins.cmd
@@ -245,6 +245,36 @@ echo %WINE_FOO%
 echo %ErrorLevel%
 set WINE_FOO=
 
+echo ------------ Testing chains ------------
+rem no spaces around the operators to avoid printing an extra space
+echo --- reference with brackets
+rem '&&' precedes '||' and '||' precedes '&'
+(echo a1&echo a2)
+(echo b1&&echo b2)
+(echo c1||echo c2)
+((echo d1&echo d2)&echo d3)
+(echo e1&(echo e2&&echo e3))
+(echo f1&(echo f2||echo f3))
+((echo g1&&echo g2)&echo g3)
+((echo h1&&echo h2)&&echo h3)
+((echo i1&&echo i2)||echo i3)
+((echo j1||echo j2)&echo j3)
+(echo k1||(echo k2&&echo k3))
+((echo l1||echo l2)||echo l3)
+echo --- with echo
+echo a1&echo a2
+echo b1&&echo b2
+echo c1||echo c2
+echo d1&echo d2&echo d3
+echo e1&echo e2&&echo e3
+echo f1&echo f2||echo f3
+echo g1&&echo g2&echo g3
+echo h1&&echo h2&&echo h3
+echo i1&&echo i2||echo i3
+echo j1||echo j2&echo j3
+echo k1||echo k2&&echo k3
+echo l1||echo l2||echo l3
+
 echo ------------ Testing 'set' ------------
 call :setError 0
 rem Remove any WINE_FOO* WINE_BA* environment variables from shell before proceeding
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp
index d01a23e..a78043c 100644
--- a/programs/cmd/tests/test_builtins.cmd.exp
+++ b/programs/cmd/tests/test_builtins.cmd.exp
@@ -241,6 +241,59 @@ WINE_FOO=bar | baz
 WINE_FOO=bar ^| baz
 bar | baz
 0
+------------ Testing chains ------------
+--- reference with brackets
+a1
+a2
+b1
+b2
+c1
+d1
+d2
+d3
+e1
+e2
+e3
+f1
+f2
+g1
+g2
+g3
+h1
+h2
+h3
+i1
+i2
+j1
+j3
+k1
+l1
+--- with echo
+a1
+a2
+b1
+b2
+c1
+d1
+d2
+d3
+e1
+e2
+e3
+f1
+f2
+g1
+g2
+g3
+h1
+h2
+h3
+i1
+i2
+j1
+j3
+k1
+l1
 ------------ Testing 'set' ------------
 1
 0
@@ -400,7 +453,7 @@ bar2 at space@
 foo2
 foobar deleted
 --- on success conditional and
- at todo_wine@foo3 not created
+foo3 not created
 bar4 at space@
 foo4
 --- on failure conditional or
diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h
index c135621..2c8a9c1 100644
--- a/programs/cmd/wcmd.h
+++ b/programs/cmd/wcmd.h
@@ -36,10 +36,11 @@
 /* Data structure to hold commands delimitors/separators */
 
 typedef enum _CMDdelimiters {
-  CMD_NONE,        /* End of line or single & */
-  CMD_ONFAILURE,   /* ||                      */
-  CMD_ONSUCCESS,   /* &&                      */
-  CMD_PIPE         /* Single |                */
+  CMD_NONE,        /* End of line */
+  CMD_ONFAILURE,   /* ||          */
+  CMD_ONSUCCESS,   /* &&          */
+  CMD_PIPE,        /* Single |    */
+  CMD_CONTINUE     /* Single &    */
 } CMD_DELIMITERS;
 
 /* Data structure to hold commands to be processed */
@@ -55,7 +56,7 @@ typedef struct _CMD_LIST {
 
 void WCMD_assoc (const WCHAR *, BOOL);
 void WCMD_batch (WCHAR *, WCHAR *, BOOL, WCHAR *, HANDLE);
-void WCMD_call (WCHAR *command);
+BOOL WCMD_call (WCHAR *command);
 void WCMD_change_tty (void);
 void WCMD_choice (const WCHAR *);
 void WCMD_clear_screen (void);
@@ -97,7 +98,7 @@ void WCMD_setshow_time (void);
 void WCMD_shift (const WCHAR *args);
 void WCMD_start (const WCHAR *args);
 void WCMD_title (const WCHAR *);
-void WCMD_type (WCHAR *);
+BOOL WCMD_type (WCHAR *);
 void WCMD_verify (const WCHAR *args);
 void WCMD_version (void);
 int  WCMD_volume (BOOL set_label, const WCHAR *args);
@@ -123,7 +124,7 @@ BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars, LPDWO
 WCHAR    *WCMD_ReadAndParseLine(const WCHAR *initialcmd, CMD_LIST **output, HANDLE readFrom);
 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket, BOOL retrycall);
 void      WCMD_free_commands(CMD_LIST *cmds);
-void      WCMD_execute (const WCHAR *orig_command, const WCHAR *redirects,
+BOOL      WCMD_execute (const WCHAR *orig_command, const WCHAR *redirects,
                         CMD_LIST **cmdList, BOOL retrycall);
 
 void *heap_alloc(size_t);
diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c
index 87f5387..e153080 100644
--- a/programs/cmd/wcmdmain.c
+++ b/programs/cmd/wcmdmain.c
@@ -1253,13 +1253,51 @@ void WCMD_run_program (WCHAR *command, BOOL called)
 }
 
 /*****************************************************************************
+ * Get the next command to be executed in the chain.
+ * It skip over comands so that '&&' precedes '||' and '||' precedes '&':
+ *   'a && b || c & b' is equivalent to '(((a && b) || c) & d)'
+ */
+CMD_LIST *WCMD_next_in_chain(CMD_LIST *thisCmd, BOOL canChain, int bdepth)
+{
+  CMD_LIST *chainCmd;
+
+  WINE_TRACE("Getting next in chain (%p, %d, %d)\n", thisCmd, canChain, bdepth);
+
+  /* Nothing to chain */
+  if (thisCmd == NULL || thisCmd->nextcommand == NULL) return NULL;
+
+  chainCmd = thisCmd->nextcommand;
+
+  /* We got a success followed by '||', so skips all '||' and '&&' */
+  if (canChain && chainCmd->prevDelim == CMD_ONFAILURE) {
+    while (chainCmd && chainCmd->bracketDepth >= bdepth &&
+           (chainCmd->prevDelim == CMD_ONFAILURE ||
+            chainCmd->prevDelim == CMD_ONSUCCESS)) {
+      WINE_TRACE("Skipping command after success (%d)\n", chainCmd->prevDelim);
+      chainCmd = chainCmd->nextcommand;
+    }
+
+  /* We got a failure, so '&&' skips all '&&' */
+  } else if (!canChain) {
+    while (chainCmd && chainCmd->bracketDepth >= bdepth &&
+           chainCmd->prevDelim == CMD_ONSUCCESS) {
+      WINE_TRACE("Skipping command after failure (%d)\n", chainCmd->prevDelim);
+      chainCmd = chainCmd->nextcommand;
+    }
+  }
+
+  /* Return the chain command that can be executed. */
+  return chainCmd;
+}
+
+/*****************************************************************************
  * Process one command. If the command is EXIT this routine does not return.
  * We will recurse through here executing batch files.
  * Note: If call is used to a non-existing program, we reparse the line and
  *       try to run it as an internal command. 'retrycall' represents whether
  *       we are attempting this retry.
  */
-void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
+BOOL WCMD_execute (const WCHAR *command, const WCHAR *redirects,
                    CMD_LIST **cmdList, BOOL retrycall)
 {
     WCHAR *cmd, *p, *redir;
@@ -1277,6 +1315,7 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
                                 STD_OUTPUT_HANDLE,
                                 STD_ERROR_HANDLE};
     BOOL prev_echo_mode, piped = FALSE;
+    BOOL canChain = TRUE;  /* Assume untested commands can chain */
 
     WINE_TRACE("command on entry:%s (%p)\n",
                wine_dbgstr_w(command), cmdList);
@@ -1349,7 +1388,7 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
       if (!status) WCMD_print_error ();
       heap_free(cmd );
       heap_free(new_redir);
-      return;
+      return canChain;
     }
 
     sa.nLength = sizeof(sa);
@@ -1370,7 +1409,7 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
           WCMD_print_error ();
           heap_free(cmd);
           heap_free(new_redir);
-          return;
+          return canChain;
         }
         SetStdHandle (STD_INPUT_HANDLE, h);
 
@@ -1385,7 +1424,7 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
 	WCMD_print_error ();
         heap_free(cmd);
         heap_free(new_redir);
-	return;
+        return canChain;
       }
       SetStdHandle (STD_INPUT_HANDLE, h);
     }
@@ -1431,7 +1470,7 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
           WCMD_print_error ();
           heap_free(cmd);
           heap_free(new_redir);
-          return;
+          return canChain;
         }
         if (SetFilePointer (h, 0, NULL, FILE_END) ==
               INVALID_SET_FILE_POINTER) {
@@ -1478,7 +1517,7 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
     switch (i) {
 
       case WCMD_CALL:
-        WCMD_call (p);
+        canChain = WCMD_call(p);
         break;
       case WCMD_CD:
       case WCMD_CHDIR:
@@ -1564,8 +1603,8 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
           WCMD_title(&whichcmd[count+1]);
         break;
       case WCMD_TYPE:
-        WCMD_type (p);
-	break;
+        canChain = WCMD_type(p);
+        break;
       case WCMD_VER:
         WCMD_output_asis(newlineW);
         WCMD_version ();
@@ -1626,6 +1665,7 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
         SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
       }
     }
+    return canChain;
 }
 
 /*************************************************************************
@@ -2147,7 +2187,7 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE
                     curPos++; /* Skip other & */
                     prevDelim = CMD_ONSUCCESS;
                   } else {
-                    prevDelim = CMD_NONE;
+                    prevDelim = CMD_CONTINUE;
                   }
                 } else {
                   curCopyTo[(*curLen)++] = *curPos;
@@ -2270,6 +2310,7 @@ CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
                                 BOOL retrycall) {
 
     int bdepth = -1;
+    BOOL canChain = TRUE;
 
     if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
 
@@ -2292,11 +2333,11 @@ CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
          Also, skip over any batch labels (eg. :fred)          */
       if (thisCmd->command && thisCmd->command[0] != ':') {
         WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
-        WCMD_execute (thisCmd->command, thisCmd->redirects, &thisCmd, retrycall);
+        canChain = WCMD_execute (thisCmd->command, thisCmd->redirects, &thisCmd, retrycall);
       }
 
       /* Step on unless the command itself already stepped on */
-      if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
+      if (thisCmd == origCmd) thisCmd = WCMD_next_in_chain(thisCmd, canChain, bdepth);
     }
     return NULL;
 }
-- 
2.7.4




More information about the wine-patches mailing list