[PATCH 5/5] [cmd] Add support for calling a built in command

Ann and Jason Edmeades jason at edmeades.me.uk
Mon Oct 15 18:53:41 CDT 2012


Add support for call <builtin command> such as call dir. This first
searches for dir.* and will execute it if possible, and then should
drop back to looking for dir as an internal command.

Note: The special casing of if and for mirrors Windows, and may be that
the if and for logic is complex. Windows will fail saying both if
and for do not exist if called, and it made things complex to try to
do anything different in wine. This was exposed by the existing tests.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.winehq.org/pipermail/wine-patches/attachments/20121016/84d97967/attachment.html>
-------------- next part --------------
From 390b9b02d6ef4e75b54116fc43bd2f6318a54bad Mon Sep 17 00:00:00 2001
From: Jason Edmeades <jason at edmeades.me.uk>
Date: Sun, 14 Oct 2012 00:38:53 +0100
Subject: [PATCH 5/5] [cmd] Add support for calling a built in command

Add support for call <builtin command> such as call dir. This first
searches for dir.* and will execute it if possible, and then should
drop back to looking for dir as an internal command.

Note: The special casing of if and for mirrors Windows, and may be that
the if and for logic is complex. Windows will fail saying both if
and for do not exist if called, and it made things complex to try to
do anything different in wine. This was exposed by the existing tests.

[Should fix bug 21381]
---
 programs/cmd/batch.c                     |    7 ++++-
 programs/cmd/builtins.c                  |   10 +++---
 programs/cmd/tests/test_builtins.cmd     |   16 ++++++++++
 programs/cmd/tests/test_builtins.cmd.exp |   19 +++++++-----
 programs/cmd/wcmd.h                      |    4 +--
 programs/cmd/wcmdmain.c                  |   50 +++++++++++++++++++++++-------
 6 files changed, 79 insertions(+), 27 deletions(-)

diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c
index 7b17ea8..bacb6cd 100644
--- a/programs/cmd/batch.c
+++ b/programs/cmd/batch.c
@@ -89,7 +89,10 @@ void WCMD_batch (WCHAR *file, WCHAR *command, BOOL called, WCHAR *startLabel, HA
       CMD_LIST *toExecute = NULL;         /* Commands left to be executed */
       if (!WCMD_ReadAndParseLine(NULL, &toExecute, h))
         break;
-      WCMD_process_commands(toExecute, FALSE, NULL, NULL);
+      /* Note: although this batch program itself may be called, we are not retrying
+         the command as a result of a call failing to find a program, hence the
+         retryCall parameter below is FALSE                                           */
+      WCMD_process_commands(toExecute, FALSE, NULL, NULL, FALSE);
       WCMD_free_commands(toExecute);
       toExecute = NULL;
   }
@@ -649,6 +652,8 @@ void WCMD_call (WCHAR *command) {
   /* Run other program if no leading ':' */
   if (*command != ':') {
     WCMD_run_program(command, TRUE);
+    /* If the thing we try to run does not exist, call returns 1 */
+    if (errorlevel) errorlevel=1;
   } else {
 
     WCHAR gotoLabel[MAX_PATH];
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c
index 866281b..8ae9eb2 100644
--- a/programs/cmd/builtins.c
+++ b/programs/cmd/builtins.c
@@ -1439,7 +1439,7 @@ static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
   /* Process the first command, if there is one */
   if (executecmds && firstcmd && *firstcmd) {
     WCHAR *command = WCMD_strdupW(firstcmd);
-    WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
+    WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList, FALSE);
     HeapFree(GetProcessHeap(), 0, command);
   }
 
@@ -1468,14 +1468,14 @@ static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
           (*cmdList)->prevDelim == CMD_ONSUCCESS) {
         if (processThese && (*cmdList)->command) {
           WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
-                        value, cmdList);
+                        value, cmdList, FALSE);
         }
         if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
 
       /* Execute any appended to the statement with (...) */
       } else if ((*cmdList)->bracketDepth > myDepth) {
         if (processThese) {
-          *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
+          *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value, FALSE);
           WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
         }
         if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
@@ -1497,7 +1497,7 @@ static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
             /* Skip leading whitespace between condition and the command */
             while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
             if (*cmd) {
-              WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
+              WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList, FALSE);
             }
           }
           if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
@@ -1809,7 +1809,7 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
 
                 /* Execute program and redirect output */
                 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
-                WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
+                WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL, FALSE);
 
                 /* Open the file, read line by line and process */
                 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd
index 1956c3f..3d01151 100644
--- a/programs/cmd/tests/test_builtins.cmd
+++ b/programs/cmd/tests/test_builtins.cmd
@@ -1599,6 +1599,22 @@ echo %ErrorLevel%
 rem First look for programs in the path before trying a builtin
 echo echo non-builtin dir> dir.cmd
 call dir /b
+del dir.cmd
+rem The below line equates to call (, which does nothing, then the
+rem subsequent lines are executed.
+call (
+  echo Line one
+  echo Line two
+)
+rem The below line equates to call if, which always fails, then the
+rem subsequent lines are executed. Note cmd.exe swallows all lines
+rem starting with )
+call if 1==1 (
+  echo Get if
+) else (
+  echo ... and else!
+)
+call call call echo passed
 cd .. & rd /s/q foobar
 
 echo ------------ Testing SHIFT ------------
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp
index f6e7d11..b7e8397 100644
--- a/programs/cmd/tests/test_builtins.cmd.exp
+++ b/programs/cmd/tests/test_builtins.cmd.exp
@@ -824,14 +824,19 @@ foo ""
 foo ''
 '' bar
 --- with builtins
- at todo_wine@0
- at todo_wine@foo created
- at todo_wine@Should expand foobaz
- at todo_wine@batfile
- at todo_wine@robinfile
- at todo_wine@1
- at todo_wine@1
+0
+foo created
+Should expand foobaz
+batfile
+robinfile
+1
+1
 non-builtin dir
+Line one
+Line two
+Get if
+... and else!
+passed
 ------------ Testing SHIFT ------------
 'p1' 'p2' 'p3' 'p4' 'p5'
 'p2' 'p3' 'p4' 'p5' ''
diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h
index e303efe..d4d148f 100644
--- a/programs/cmd/wcmd.h
+++ b/programs/cmd/wcmd.h
@@ -121,11 +121,11 @@ 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,
-                                const WCHAR *var, const WCHAR *val);
+                                const WCHAR *var, const WCHAR *val, BOOL retrycall);
 void      WCMD_free_commands(CMD_LIST *cmds);
 void      WCMD_execute (const WCHAR *orig_command, const WCHAR *redirects,
                         const WCHAR *parameter, const WCHAR *substitution,
-                        CMD_LIST **cmdList);
+                        CMD_LIST **cmdList, BOOL retrycall);
 
 /* Data structure to hold context when executing batch files */
 
diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c
index 07b8321..6931755 100644
--- a/programs/cmd/wcmdmain.c
+++ b/programs/cmd/wcmdmain.c
@@ -1000,6 +1000,9 @@ static void init_msvcrt_io_block(STARTUPINFOW* st)
  *      Launching
  *        Once a match has been found, it is launched - Code currently uses
  *          findexecutable to achieve this which is left untouched.
+ *        If an executable has not been found, and we were launched through
+ *          a call, we need to check if the command is an internal command,
+ *          so go back through wcmd_execute.
  */
 
 void WCMD_run_program (WCHAR *command, BOOL called)
@@ -1197,6 +1200,8 @@ void WCMD_run_program (WCHAR *command, BOOL called)
         if (!status)
           break;
 
+        called = FALSE; /* No need to retry as we launched something */
+
         if (!assumeInternal && !console) errorlevel = 0;
         else
         {
@@ -1212,6 +1217,18 @@ void WCMD_run_program (WCHAR *command, BOOL called)
     }
   }
 
+  /* Not found anywhere - were we called? */
+  if (called) {
+    CMD_LIST *toExecute = NULL;         /* Commands left to be executed */
+
+    /* Parse the command string, without reading any more input */
+    WCMD_ReadAndParseLine(command, &toExecute, INVALID_HANDLE_VALUE);
+    WCMD_process_commands(toExecute, FALSE, NULL, NULL, called);
+    WCMD_free_commands(toExecute);
+    toExecute = NULL;
+    return;
+  }
+
   /* Not found anywhere - give up */
   SetLastError(ERROR_FILE_NOT_FOUND);
   WCMD_print_error ();
@@ -1226,10 +1243,13 @@ void WCMD_run_program (WCHAR *command, BOOL called)
 /*****************************************************************************
  * 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,
                    const WCHAR *forVariable, const WCHAR *forValue,
-                   CMD_LIST **cmdList)
+                   CMD_LIST **cmdList, BOOL retrycall)
 {
     WCHAR *cmd, *p, *redir;
     int status, i;
@@ -1487,18 +1507,12 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
       case WCMD_ECHO:
         WCMD_echo(&whichcmd[count]);
         break;
-      case WCMD_FOR:
-        WCMD_for (p, cmdList);
-        break;
       case WCMD_GOTO:
         WCMD_goto (cmdList);
         break;
       case WCMD_HELP:
         WCMD_give_help (p);
 	break;
-      case WCMD_IF:
-	WCMD_if (p, cmdList);
-        break;
       case WCMD_LABEL:
         WCMD_volume (TRUE, p);
         break;
@@ -1587,6 +1601,17 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
       case WCMD_EXIT:
         WCMD_exit (cmdList);
         break;
+      case WCMD_FOR:
+      case WCMD_IF:
+        /* Very oddly, probably because of all the special parsing required for
+           these two commands, neither for nor if are supported when called,
+           ie call if 1==1... will fail.                                        */
+        if (!retrycall) {
+          if (i==WCMD_FOR) WCMD_for (p, cmdList);
+          else if (i==WCMD_IF) WCMD_if (p, cmdList);
+          break;
+        }
+        /* else: drop through */
       default:
         prev_echo_mode = echo_mode;
         WCMD_run_program (whichcmd, FALSE);
@@ -2240,7 +2265,8 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE
  * Process all the commands read in so far
  */
 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
-                                const WCHAR *var, const WCHAR *val) {
+                                const WCHAR *var, const WCHAR *val,
+                                BOOL retrycall) {
 
     int bdepth = -1;
 
@@ -2265,7 +2291,7 @@ 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, var, val, &thisCmd);
+        WCMD_execute (thisCmd->command, thisCmd->redirects, var, val, &thisCmd, retrycall);
       }
 
       /* Step on unless the command itself already stepped on */
@@ -2555,7 +2581,7 @@ int wmain (int argc, WCHAR *argvW[])
 
       /* Parse the command string, without reading any more input */
       WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
-      WCMD_process_commands(toExecute, FALSE, NULL, NULL);
+      WCMD_process_commands(toExecute, FALSE, NULL, NULL, FALSE);
       WCMD_free_commands(toExecute);
       toExecute = NULL;
 
@@ -2642,7 +2668,7 @@ int wmain (int argc, WCHAR *argvW[])
   if (opt_k) {
       /* Parse the command string, without reading any more input */
       WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
-      WCMD_process_commands(toExecute, FALSE, NULL, NULL);
+      WCMD_process_commands(toExecute, FALSE, NULL, NULL, FALSE);
       WCMD_free_commands(toExecute);
       toExecute = NULL;
       HeapFree(GetProcessHeap(), 0, cmd);
@@ -2662,7 +2688,7 @@ int wmain (int argc, WCHAR *argvW[])
     if (echo_mode) WCMD_show_prompt();
     if (!WCMD_ReadAndParseLine(NULL, &toExecute, GetStdHandle(STD_INPUT_HANDLE)))
       break;
-    WCMD_process_commands(toExecute, FALSE, NULL, NULL);
+    WCMD_process_commands(toExecute, FALSE, NULL, NULL, FALSE);
     WCMD_free_commands(toExecute);
     toExecute = NULL;
   }
-- 
1.7.9.5


More information about the wine-patches mailing list