[PATCH 2/5] cmd: use GetCommandline() rather than argv (try 5)

Martin Wilck mwilck at arcor.de
Thu Sep 29 15:10:49 CDT 2011


Windows CMD.EXE doesn't use argc/argv for the command line arguments.
Automatic argv processing in Wine strips most quotes from the command
line, leading to wrong results (try cmd /c @echo "hi").

This patch makes CMD use GetCommandLine() instead. Some code that
served to work around argv parsing artefacts can be dropped.

This patch is quite large because the different way to obtain command
line arguments implies a lot of changes to avoid regressions. However
this patch removes more code than it adds.
---
 programs/cmd/tests/test_cmdline.cmd     |    3 +-
 programs/cmd/tests/test_cmdline.cmd.exp |   20 ++--
 programs/cmd/wcmdmain.c                 |  247 +++++++++++--------------------
 3 files changed, 99 insertions(+), 171 deletions(-)

diff --git a/programs/cmd/tests/test_cmdline.cmd b/programs/cmd/tests/test_cmdline.cmd
index d1fbba1..a9e0260 100644
--- a/programs/cmd/tests/test_cmdline.cmd
+++ b/programs/cmd/tests/test_cmdline.cmd
@@ -85,8 +85,7 @@ cmd /c"say one"
 rem ignore quote before qualifier
 cmd "/c"say one
 rem ignore anything before /c
-rem FIXME the next command in wine starts a sub-CMD
-echo THIS FAILS: cmd ignoreme/c say one
+cmd ignoreme/c say one
 echo --------- Testing special characters --------------
 echo @echo amp > "say&.bat"
 call say&
diff --git a/programs/cmd/tests/test_cmdline.cmd.exp b/programs/cmd/tests/test_cmdline.cmd.exp
index 9bc41a4..de8d575 100644
--- a/programs/cmd/tests/test_cmdline.cmd.exp
+++ b/programs/cmd/tests/test_cmdline.cmd.exp
@@ -15,25 +15,25 @@
 0 at space@
 1 at space@
 @todo_wine at 0@space@
- at todo_wine@0 at space@
-1 at space@
 0 at space@
-1 at space@
-2 at space@
+ at todo_wine@1 at space@
+0 at space@
+ at todo_wine@1 at space@
+ at todo_wine@2 at space@
 0 at space@
 @todo_wine at 3@space@
 @todo_wine at 4@space@
 ---------- Testing CMD /C quoting -----------------
- at todo_wine@"hi"
+"hi"
+ at todo_wine@1 at space@
+"\"\\"\\\"\\\\"@space@"\"\\"\\\"\\\\"
 1 at space@
- at todo_wine@"\"\\"\\\"\\\\"@space@"\"\\"\\\"\\\\"
+0 at space@
 1 at space@
 0 at space@
- at todo_wine@1 at space@
- at todo_wine@0 at space@
 @todo_wine at 0@space@
 0 at space@@or_broken at 3@space@
- at todo_wine@3 at space@
+3 at space@
 2 at space@
 2 at space@
 2 at space@
@@ -43,7 +43,7 @@
 0 at space@
 1 at space@
 0 at space@
-THIS at space@FAILS:@space at cmd@space at ignoreme/c at space@say at space@one
+0 at space@
 --------- Testing special characters --------------
 0 at space@
 @todo_wine at 0@space@
diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c
index 3f2cfcf..e78bac5 100644
--- a/programs/cmd/wcmdmain.c
+++ b/programs/cmd/wcmdmain.c
@@ -2269,6 +2269,68 @@ void WCMD_free_commands(CMD_LIST *cmds) {
     }
 }
 
+/*
+ * Helper for wmain - Skips program name from command line.
+ */
+static const WCHAR*  skip_program_name(const WCHAR *cmdline)
+{
+    const WCHAR *p;
+    BOOL in_quotes;
+    for (p = cmdline, in_quotes = FALSE; *p != '\0'; p++) {
+        if (!in_quotes && (*p == ' ' || *p == '\t')) {
+            do { p++; } while (*p == ' ' || *p == '\t');
+            break;
+        }
+        if (*p == '"')
+            in_quotes = !in_quotes;
+    }
+    return p;
+}
+
+/*
+ * If the first /c or /k command character is a quote, cmd follows
+ * a complex logic to decide whether quotes will be kept:
+ *   1. no /s switch
+ *   2. exactly 2 quotes
+ *   3. at least 1 whitespace between the quotes
+ *   4. no special char (see below) between the quotes
+ *   5. string between quotes is a valid exe or bat file
+ * This function checks 2.-4., 5. is checked in WCMD_run_program().
+ */
+static BOOL may_keep_quotes(const WCHAR *chr)
+{
+    const WCHAR *pt, *pq;
+    BOOL spc;
+
+    for (pt = chr+1, spc = FALSE, pq = NULL; *pt != '\0'; pt++) {
+          if (*pt == '"') {
+              if (pq != NULL)
+                  /* don't keep if > 2 quotes */
+                  return FALSE;
+              pq = pt;
+              continue;
+          }
+          if (pq != NULL)
+              /* already behind closing quote */
+              continue;
+          switch (*pt) {
+          case ' ': case '\t':
+              spc = TRUE;
+              break;
+          /* don't keep if text between quotes contains special character */
+          case '&': case '<': case '>': case '(':
+          case ')': case '@': case '^': case '|':
+              return FALSE;
+          default:
+              break;
+          }
+    }
+
+    /* don't keep if no space between the quotes, or if < 2 quotes */
+    if (!spc || pq == NULL)
+        return FALSE;
+    return TRUE;
+}
 
 /*****************************************************************************
  * Main entry point. This is a console application so we have a main() not a
@@ -2277,7 +2339,6 @@ void WCMD_free_commands(CMD_LIST *cmds) {
 
 int wmain (int argc, WCHAR *argvW[])
 {
-  int     args;
   WCHAR  *cmd   = NULL;
   WCHAR string[1024];
   WCHAR envvar[4];
@@ -2289,6 +2350,7 @@ int wmain (int argc, WCHAR *argvW[])
   static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'};
   char ansiVersion[100];
   CMD_LIST *toExecute = NULL;         /* Commands left to be executed */
+  WCHAR *cmdline, *chr;
 
   srand(time(NULL));
 
@@ -2298,19 +2360,22 @@ int wmain (int argc, WCHAR *argvW[])
   wsprintfW(version_string, WCMD_LoadMessage(WCMD_VERSION), string);
   strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
 
-  args  = argc;
+  cmdline = WCMD_strdupW(GetCommandLineW());
+  if (cmdline == NULL) {
+      WINE_ERR("Out of memory\n");
+      exit(1);
+  }
+  chr = (WCHAR*)skip_program_name(cmdline);
+  WINE_TRACE("Command line: '%s'\n", wine_dbgstr_w(chr));
+
   opt_c=opt_k=opt_q=opt_s=0;
-  while (args > 0)
-  {
-      WCHAR c;
-      WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW));
-      if ((*argvW)[0]!='/' || (*argvW)[1]=='\0') {
-          argvW++;
-          args--;
+  for (; *chr != '\0'; chr++) {
+      WCHAR c, *t;
+      if (*chr != '/')
           continue;
-      }
-
-      c=(*argvW)[1];
+      c = *(++chr);
+      if (c == '\0')
+          break;
       if (tolowerW(c)=='c') {
           opt_c=1;
       } else if (tolowerW(c)=='q') {
@@ -2323,21 +2388,15 @@ int wmain (int argc, WCHAR *argvW[])
           unicodePipes=FALSE;
       } else if (tolowerW(c)=='u') {
           unicodePipes=TRUE;
-      } else if (tolowerW(c)=='t' && (*argvW)[2]==':') {
-          opt_t=strtoulW(&(*argvW)[3], NULL, 16);
+      } else if (tolowerW(c)=='t' && *(chr+1) == ':') {
+          opt_t=strtoulW(chr+2, &t, 16);
+          if (t == chr+2)
+              opt_t = 0;
+          chr = t-1; /* will be incremented in for loop */
       } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
           /* Ignored for compatibility with Windows */
       }
 
-      if ((*argvW)[2]==0) {
-          argvW++;
-          args--;
-      }
-      else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
-      {
-          *argvW+=2;
-      }
-
       if (opt_c || opt_k) /* break out of parsing immediately after c or k */
           break;
   }
@@ -2348,147 +2407,17 @@ int wmain (int argc, WCHAR *argvW[])
   }
 
   if (opt_c || opt_k) {
-      int     len,qcount;
-      WCHAR** arg;
-      int     argsLeft;
-      WCHAR*  p;
-
-      /* opt_s left unflagged if the command starts with and contains exactly
-       * one quoted string (exactly two quote characters). The quoted string
-       * must be an executable name that has whitespace and must not have the
-       * following characters: &<>()@^| */
-
-      /* Build the command to execute */
-      len = 0;
-      qcount = 0;
-      argsLeft = args;
-      for (arg = argvW; argsLeft>0; arg++,argsLeft--)
-      {
-          int has_space,bcount;
-          WCHAR* a;
-
-          has_space=0;
-          bcount=0;
-          a=*arg;
-          if( !*a ) has_space=1;
-          while (*a!='\0') {
-              if (*a=='\\') {
-                  bcount++;
-              } else {
-                  if (*a==' ' || *a=='\t') {
-                      has_space=1;
-                  } else if (*a=='"') {
-                      /* doubling of '\' preceding a '"',
-                       * plus escaping of said '"'
-                       */
-                      len+=2*bcount+1;
-                      qcount++;
-                  }
-                  bcount=0;
-              }
-              a++;
-          }
-          len+=(a-*arg) + 1; /* for the separating space */
-          if (has_space)
-          {
-              len+=2; /* for the quotes */
-              qcount+=2;
-          }
-      }
-
-      if (qcount!=2)
-          opt_s=1;
 
-      /* check argvW[0] for a space and invalid characters */
-      if (!opt_s) {
-          opt_s=1;
-          p=*argvW;
-          while (*p!='\0') {
-              if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
-                  || *p=='@' || *p=='^' || *p=='|') {
-                  opt_s=1;
-                  break;
-              }
-              if (*p==' ')
-                  opt_s=0;
-              p++;
-          }
-      }
+      for (cmd = chr+1; *cmd == ' ' || *cmd == '\t'; cmd++);
 
-      cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
-      if (!cmd)
-          exit(1);
-
-      p = cmd;
-      argsLeft = args;
-      for (arg = argvW; argsLeft>0; arg++,argsLeft--)
-      {
-          int has_space,has_quote;
-          WCHAR* a;
-
-          /* Check for quotes and spaces in this argument */
-          has_space=has_quote=0;
-          a=*arg;
-          if( !*a ) has_space=1;
-          while (*a!='\0') {
-              if (*a==' ' || *a=='\t') {
-                  has_space=1;
-                  if (has_quote)
-                      break;
-              } else if (*a=='"') {
-                  has_quote=1;
-                  if (has_space)
-                      break;
-              }
-              a++;
-          }
-
-          /* Now transfer it to the command line */
-          if (has_space)
-              *p++='"';
-          if (has_quote) {
-              int bcount;
-              WCHAR* a;
-
-              bcount=0;
-              a=*arg;
-              while (*a!='\0') {
-                  if (*a=='\\') {
-                      *p++=*a;
-                      bcount++;
-                  } else {
-                      if (*a=='"') {
-                          int i;
-
-                          /* Double all the '\\' preceding this '"', plus one */
-                          for (i=0;i<=bcount;i++)
-                              *p++='\\';
-                          *p++='"';
-                      } else {
-                          *p++=*a;
-                      }
-                      bcount=0;
-                  }
-                  a++;
-              }
-          } else {
-              strcpyW(p,*arg);
-              p+=strlenW(*arg);
-          }
-          if (has_space)
-              *p++='"';
-          *p++=' ';
-      }
-      if (p > cmd)
-          p--;  /* remove last space */
-      *p = '\0';
-
-      WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
+      opt_s |= !may_keep_quotes(cmd);
 
       /* strip first and last quote characters if opt_s; check for invalid
        * executable is done later */
       if (opt_s && *cmd=='\"')
           WCMD_opt_s_strip_quotes(cmd);
+
+      WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
   }
 
   if (opt_c) {
@@ -2504,7 +2433,7 @@ int wmain (int argc, WCHAR *argvW[])
       WCMD_free_commands(toExecute);
       toExecute = NULL;
 
-      HeapFree(GetProcessHeap(), 0, cmd);
+      HeapFree(GetProcessHeap(), 0, (void*)cmdline);
       return errorlevel;
   }
 
@@ -2599,13 +2528,13 @@ int wmain (int argc, WCHAR *argvW[])
       WCMD_process_commands(toExecute, FALSE, NULL, NULL);
       WCMD_free_commands(toExecute);
       toExecute = NULL;
-      HeapFree(GetProcessHeap(), 0, cmd);
   }
 
 /*
  *	Loop forever getting commands and executing them.
  */
 
+  HeapFree(GetProcessHeap(), 0, (void*)cmdline);
   SetEnvironmentVariableW(promptW, defaultpromptW);
   WCMD_version ();
   is_console = !!GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &dummy);
-- 
1.7.3.4



More information about the wine-patches mailing list