[v2] cmd: Command chain fixes

Flávio J. Saraiva flaviojs2005 at gmail.com
Mon Nov 7 18:33:17 CST 2016


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

This commit affects 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 to 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'


It has a very naive branch depth approach because fixing that also
requires the IF/FOR changes, so the fix is not part this commit.

Sample error case:
  'echo 1||(echo 2&echo 3)'


The TYPE and CALL commands were updated because the current tests
required them. Other commands either always chain, which is the
default behavior of this code, or are not being tested in chains.

Make TYPE also set errorlevel 1 with no arguments.
Make CALL chain when errorlevel is 0.

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                      | 16 ++++----
 programs/cmd/wcmdmain.c                  | 70 ++++++++++++++++++++++++++------
 6 files changed, 161 insertions(+), 23 deletions(-)

diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c
index 1a78b55..280d5e2 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 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..400e78f 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);
@@ -117,13 +118,14 @@ void WCMD_HandleTildaModifiers(WCHAR **start, BOOL atExecute);
 void WCMD_splitpath(const WCHAR* path, WCHAR* drv, WCHAR* dir, WCHAR* name, WCHAR* ext);
 WCHAR *WCMD_strip_quotes(WCHAR *cmd);
 WCHAR *WCMD_LoadMessage(UINT id);
+void WCMD_DumpCommands(CMD_LIST *commands);
 void WCMD_strsubstW(WCHAR *start, const WCHAR* next, const WCHAR* insert, int len);
 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars, LPDWORD charsRead);
 
 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..1e150cf 100644
--- a/programs/cmd/wcmdmain.c
+++ b/programs/cmd/wcmdmain.c
@@ -1253,13 +1253,56 @@ void WCMD_run_program (WCHAR *command, BOOL called)
 }
 
 /*****************************************************************************
+ * Get the next command to be executed.
+ * It skip over comands so that '&&' precedes '||' and '||' precedes '&':
+ *   'a && b || c & d' is equivalent to '(((a && b) || c) & d)'
+ *
+ * FIXME this command has a very naive bracket depth approach, and fails
+ *       combinations like 'echo 1||(echo 2&echo 3)', but that can only
+ *       be fixed after IF/FOR parsing is improved
+ */
+CMD_LIST *WCMD_next_command (CMD_LIST *thisCmd, BOOL result, int bdepth)
+{
+  CMD_LIST *nextCmd;
+
+  WINE_TRACE("Getting next command (result=%d, bdepth=%d)\n", result, bdepth);
+  WCMD_DumpCommands(thisCmd);
+
+  /* Nothing to chain */
+  if (thisCmd == NULL || thisCmd->nextcommand == NULL) return NULL;
+
+  nextCmd = thisCmd->nextcommand;
+
+  /* We got a success followed by '||', so skips all '||' and '&&' */
+  if (result && nextCmd->prevDelim == CMD_ONFAILURE) {
+    while (nextCmd && nextCmd->bracketDepth >= bdepth &&
+           (nextCmd->prevDelim == CMD_ONFAILURE ||
+            nextCmd->prevDelim == CMD_ONSUCCESS)) {
+      WINE_TRACE("Skipping command after success (%d)\n", nextCmd->prevDelim);
+      nextCmd = nextCmd->nextcommand;
+    }
+
+  /* We got a failure, so '&&' skips all '&&' */
+  } else if (!result) {
+    while (nextCmd && nextCmd->bracketDepth >= bdepth &&
+           nextCmd->prevDelim == CMD_ONSUCCESS) {
+      WINE_TRACE("Skipping command after failure (%d)\n", nextCmd->prevDelim);
+      nextCmd = nextCmd->nextcommand;
+    }
+  }
+
+  /* Return the next command that can be executed. */
+  return nextCmd;
+}
+
+/*****************************************************************************
  * 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 +1320,7 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
                                 STD_OUTPUT_HANDLE,
                                 STD_ERROR_HANDLE};
     BOOL prev_echo_mode, piped = FALSE;
+    BOOL result = TRUE;  /* Commands can chain by default */
 
     WINE_TRACE("command on entry:%s (%p)\n",
                wine_dbgstr_w(command), cmdList);
@@ -1349,7 +1393,7 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
       if (!status) WCMD_print_error ();
       heap_free(cmd );
       heap_free(new_redir);
-      return;
+      return result;
     }
 
     sa.nLength = sizeof(sa);
@@ -1370,7 +1414,7 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
           WCMD_print_error ();
           heap_free(cmd);
           heap_free(new_redir);
-          return;
+          return result;
         }
         SetStdHandle (STD_INPUT_HANDLE, h);
 
@@ -1385,7 +1429,7 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
 	WCMD_print_error ();
         heap_free(cmd);
         heap_free(new_redir);
-	return;
+        return result;
       }
       SetStdHandle (STD_INPUT_HANDLE, h);
     }
@@ -1431,7 +1475,7 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
           WCMD_print_error ();
           heap_free(cmd);
           heap_free(new_redir);
-          return;
+          return result;
         }
         if (SetFilePointer (h, 0, NULL, FILE_END) ==
               INVALID_SET_FILE_POINTER) {
@@ -1478,7 +1522,7 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
     switch (i) {
 
       case WCMD_CALL:
-        WCMD_call (p);
+        result = WCMD_call(p);
         break;
       case WCMD_CD:
       case WCMD_CHDIR:
@@ -1564,8 +1608,8 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
           WCMD_title(&whichcmd[count+1]);
         break;
       case WCMD_TYPE:
-        WCMD_type (p);
-	break;
+        result = WCMD_type(p);
+        break;
       case WCMD_VER:
         WCMD_output_asis(newlineW);
         WCMD_version ();
@@ -1626,6 +1670,7 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
         SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
       }
     }
+    return result;
 }
 
 /*************************************************************************
@@ -1649,7 +1694,7 @@ WCHAR *WCMD_LoadMessage(UINT id) {
  *
  *	Dumps out the parsed command line to ensure syntax is correct
  */
-static void WCMD_DumpCommands(CMD_LIST *commands) {
+void WCMD_DumpCommands(CMD_LIST *commands) {
     CMD_LIST *thisCmd = commands;
 
     WINE_TRACE("Parsed line:\n");
@@ -2147,7 +2192,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 +2315,7 @@ CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
                                 BOOL retrycall) {
 
     int bdepth = -1;
+    BOOL result = TRUE;
 
     if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
 
@@ -2292,11 +2338,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);
+        result = 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_command(thisCmd, result, bdepth);
     }
     return NULL;
 }
-- 
2.7.4




More information about the wine-patches mailing list