[PATCH 3/5] [programes/xcopy] Handle multiple switches concatenated without whitespace

Jason Edmeades us at edmeades.me.uk
Sun Jun 24 15:44:12 CDT 2018


Fixes bug#44967

This changes xcopy to support flags supplied like /S/E without spaces
between them. Tests supplied but in writing the tests I found a few
other problems left as todo in this patch.

(Note the majority of this patch is just changing the indenting of
a large code block)

Signed-off-by: Jason Edmeades <us at edmeades.me.uk>
---
 programs/xcopy/tests/xcopy.c |  39 +++++++
 programs/xcopy/xcopy.c       | 208 ++++++++++++++++++++---------------
 2 files changed, 159 insertions(+), 88 deletions(-)

diff --git a/programs/xcopy/tests/xcopy.c b/programs/xcopy/tests/xcopy.c
index ec7683aecc..7ca32d6c33 100644
--- a/programs/xcopy/tests/xcopy.c
+++ b/programs/xcopy/tests/xcopy.c
@@ -73,6 +73,44 @@ static void test_date_format(void)
     DeleteFileA("xcopytest\\xcopy1");
 }
 
+static void test_parms_syntax(void)
+{
+    DWORD rc;
+
+    rc = runcmd("xcopy /H/D:20-01-2000 xcopy1 xcopytest");
+    ok(rc == 4, "xcopy /H/D:d-m-y test returned rc=%u\n", rc);
+    ok(GetFileAttributesA("xcopytest\\xcopy1") == INVALID_FILE_ATTRIBUTES,
+       "xcopy should not have created xcopytest\\xcopy1\n");
+
+    rc = runcmd("xcopy /D:01-20-2000/H xcopy1 xcopytest");
+    ok(rc == 0, "xcopy /H/D:m-d-y test failed rc=%u\n", rc);
+    ok(GetFileAttributesA("xcopytest\\xcopy1") != INVALID_FILE_ATTRIBUTES,
+       "xcopy did not create xcopytest\\xcopy1\n");
+    DeleteFileA("xcopytest\\xcopy1");
+
+    /* The following test is commented out as under wine it generates
+       a recursively deep directory tree (todo_wine)
+    rc = runcmd("xcopy /D:1-20-2000/E xcopy1 xcopytest");
+    ok(rc == 0, "xcopy /D:m-d-y/E test failed rc=%u\n", rc);
+    ok(GetFileAttributesA("xcopytest\\xcopy1") != INVALID_FILE_ATTRIBUTES,
+       "xcopy did not create xcopytest\\xcopy1\n");
+    DeleteFileA("xcopytest\\xcopy1"); */
+
+    rc = runcmd("xcopy /D/S xcopytest xcopytest2\\");
+    todo_wine
+    ok(rc == 0, "xcopy /D/S test failed rc=%u\n", rc);
+    ok(GetFileAttributesA("xcopytest2") == INVALID_FILE_ATTRIBUTES,
+       "xcopy copied empty directory incorrectly\n");
+
+    rc = runcmd("xcopy /D/S/E xcopytest xcopytest2\\");
+    todo_wine {
+    ok(rc == 0, "xcopy /D/S/E test failed rc=%u\n", rc);
+    ok(GetFileAttributesA("xcopytest2") != INVALID_FILE_ATTRIBUTES,
+       "xcopy failed to copy empty directory\n");
+    }
+    RemoveDirectoryA("xcopytest2");
+}
+
 START_TEST(xcopy)
 {
     char tmpdir[MAX_PATH];
@@ -94,6 +132,7 @@ START_TEST(xcopy)
     CloseHandle(hfile);
 
     test_date_format();
+    test_parms_syntax();
 
     DeleteFileA("xcopy1");
     RemoveDirectoryA("xcopytest");
diff --git a/programs/xcopy/xcopy.c b/programs/xcopy/xcopy.c
index a173cc14c7..461104a8da 100644
--- a/programs/xcopy/xcopy.c
+++ b/programs/xcopy/xcopy.c
@@ -743,101 +743,133 @@ static int XCOPY_ParseCommandLine(WCHAR *suppliedsource,
                  Note: Windows docs say /P prompts when dest is created
                        but tests show it is done for each src file
                        regardless of the destination                   */
-            switch (toupper(word[1])) {
-            case 'I': flags |= OPT_ASSUMEDIR;     break;
-            case 'S': flags |= OPT_RECURSIVE;     break;
-            case 'Q': flags |= OPT_QUIET;         break;
-            case 'F': flags |= OPT_FULL;          break;
-            case 'L': flags |= OPT_SIMULATE;      break;
-            case 'W': flags |= OPT_PAUSE;         break;
-            case 'T': flags |= OPT_NOCOPY | OPT_RECURSIVE; break;
-            case 'Y': flags |= OPT_NOPROMPT;      break;
-            case 'N': flags |= OPT_SHORTNAME;     break;
-            case 'U': flags |= OPT_MUSTEXIST;     break;
-            case 'R': flags |= OPT_REPLACEREAD;   break;
-            case 'H': flags |= OPT_COPYHIDSYS;    break;
-            case 'C': flags |= OPT_IGNOREERRORS;  break;
-            case 'P': flags |= OPT_SRCPROMPT;     break;
-            case 'A': flags |= OPT_ARCHIVEONLY;   break;
-            case 'M': flags |= OPT_ARCHIVEONLY |
-                               OPT_REMOVEARCH;    break;
-
-            /* E can be /E or /EXCLUDE */
-            case 'E': if (CompareStringW(LOCALE_USER_DEFAULT,
-                                         NORM_IGNORECASE | SORT_STRINGSORT,
-                                         &word[1], 8,
-                                         EXCLUDE, -1) == CSTR_EQUAL) {
-                        if (XCOPY_ProcessExcludeList(&word[9])) {
-                          XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
-                          goto out;
-                        } else flags |= OPT_EXCLUDELIST;
-                      } else flags |= OPT_EMPTYDIR | OPT_RECURSIVE;
-                      break;
-
-            /* D can be /D or /D: */
-            case 'D': if (word[2]==':' && is_digit(word[3])) {
-                          SYSTEMTIME st;
-                          WCHAR     *pos = &word[3];
-                          BOOL       isError = FALSE;
-                          memset(&st, 0x00, sizeof(st));
-
-                          /* Microsoft xcopy's usage message implies that the date
-                           * format depends on the locale, but that is false.
-                           * It is hardcoded to month-day-year.
-                           */
-                          st.wMonth = _wtol(pos);
-                          while (*pos && is_digit(*pos)) pos++;
-                          if (*pos++ != '-') isError = TRUE;
-
-                          if (!isError) {
-                              st.wDay = _wtol(pos);
-                              while (*pos && is_digit(*pos)) pos++;
-                              if (*pos++ != '-') isError = TRUE;
-                          }
+            int skip=0;
+            WCHAR *rest;
+
+            while (word[0]) {
+                rest = NULL;
+
+                switch (toupper(word[1])) {
+                case 'I': flags |= OPT_ASSUMEDIR;     break;
+                case 'S': flags |= OPT_RECURSIVE;     break;
+                case 'Q': flags |= OPT_QUIET;         break;
+                case 'F': flags |= OPT_FULL;          break;
+                case 'L': flags |= OPT_SIMULATE;      break;
+                case 'W': flags |= OPT_PAUSE;         break;
+                case 'T': flags |= OPT_NOCOPY | OPT_RECURSIVE; break;
+                case 'Y': flags |= OPT_NOPROMPT;      break;
+                case 'N': flags |= OPT_SHORTNAME;     break;
+                case 'U': flags |= OPT_MUSTEXIST;     break;
+                case 'R': flags |= OPT_REPLACEREAD;   break;
+                case 'H': flags |= OPT_COPYHIDSYS;    break;
+                case 'C': flags |= OPT_IGNOREERRORS;  break;
+                case 'P': flags |= OPT_SRCPROMPT;     break;
+                case 'A': flags |= OPT_ARCHIVEONLY;   break;
+                case 'M': flags |= OPT_ARCHIVEONLY |
+                                   OPT_REMOVEARCH;    break;
+
+                /* E can be /E or /EXCLUDE */
+                case 'E': if (CompareStringW(LOCALE_USER_DEFAULT,
+                                             NORM_IGNORECASE | SORT_STRINGSORT,
+                                             &word[1], 8,
+                                             EXCLUDE, -1) == CSTR_EQUAL) {
+                            if (XCOPY_ProcessExcludeList(&word[9])) {
+                              XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
+                              goto out;
+                            } else {
+                              flags |= OPT_EXCLUDELIST;
 
-                          if (!isError) {
-                              st.wYear = _wtol(pos);
-                              while (*pos && is_digit(*pos)) pos++;
-                              if (st.wYear < 100) st.wYear+=2000;
+                              /* Do not support concatenated switches onto exclude lists yet */
+                              rest = end;
+                            }
+                          } else {
+                              flags |= OPT_EMPTYDIR | OPT_RECURSIVE;
                           }
+                          break;
 
-                          if (!isError && SystemTimeToFileTime(&st, &dateRange)) {
+                /* D can be /D or /D: */
+                case 'D': if (word[2]==':' && is_digit(word[3])) {
                               SYSTEMTIME st;
-                              WCHAR datestring[32], timestring[32];
-
-                              flags |= OPT_DATERANGE;
-
-                              /* Debug info: */
-                              FileTimeToSystemTime (&dateRange, &st);
-                              GetDateFormatW(0, DATE_SHORTDATE, &st, NULL, datestring,
-                                             sizeof(datestring)/sizeof(WCHAR));
-                              GetTimeFormatW(0, TIME_NOSECONDS, &st,
-                                             NULL, timestring, sizeof(timestring)/sizeof(WCHAR));
+                              WCHAR     *pos = &word[3];
+                              BOOL       isError = FALSE;
+                              memset(&st, 0x00, sizeof(st));
+
+                              /* Microsoft xcopy's usage message implies that the date
+                               * format depends on the locale, but that is false.
+                               * It is hardcoded to month-day-year.
+                               */
+                              st.wMonth = _wtol(pos);
+                              while (*pos && is_digit(*pos)) pos++;
+                              if (*pos++ != '-') isError = TRUE;
 
-                              WINE_TRACE("Date being used is: %s %s\n",
-                                         wine_dbgstr_w(datestring), wine_dbgstr_w(timestring));
+                              if (!isError) {
+                                  st.wDay = _wtol(pos);
+                                  while (*pos && is_digit(*pos)) pos++;
+                                  if (*pos++ != '-') isError = TRUE;
+                              }
+
+                              if (!isError) {
+                                  st.wYear = _wtol(pos);
+                                  while (*pos && is_digit(*pos)) pos++;
+                                  if (st.wYear < 100) st.wYear+=2000;
+                              }
+
+                              /* Handle switches straight after the supplied date */
+                              rest = pos;
+
+                              if (!isError && SystemTimeToFileTime(&st, &dateRange)) {
+                                  SYSTEMTIME st;
+                                  WCHAR datestring[32], timestring[32];
+
+                                  flags |= OPT_DATERANGE;
+
+                                  /* Debug info: */
+                                  FileTimeToSystemTime (&dateRange, &st);
+                                  GetDateFormatW(0, DATE_SHORTDATE, &st, NULL, datestring,
+                                                 sizeof(datestring)/sizeof(WCHAR));
+                                  GetTimeFormatW(0, TIME_NOSECONDS, &st,
+                                                 NULL, timestring, sizeof(timestring)/sizeof(WCHAR));
+
+                                  WINE_TRACE("Date being used is: %s %s\n",
+                                             wine_dbgstr_w(datestring), wine_dbgstr_w(timestring));
+                              } else {
+                                  XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
+                                  goto out;
+                              }
                           } else {
-                              XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
-                              goto out;
+                              flags |= OPT_DATENEWER;
                           }
-                      } else {
-                          flags |= OPT_DATENEWER;
-                      }
-                      break;
-
-            case '-': if (toupper(word[2])=='Y')
-                          flags &= ~OPT_NOPROMPT;
-                      break;
-            case '?': XCOPY_wprintf(XCOPY_LoadMessage(STRING_HELP));
-                      rc = RC_HELP;
-                      goto out;
-            case 'V':
-                WINE_FIXME("ignoring /V\n");
-                break;
-            default:
-                WINE_TRACE("Unhandled parameter '%s'\n", wine_dbgstr_w(word));
-                XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARM), word);
-                goto out;
+                          break;
+
+                case '-': if (toupper(word[2])=='Y') {
+                              flags &= ~OPT_NOPROMPT;
+                              rest = &word[3];  /* Skip over 3 characters */
+                          }
+                          break;
+                case '?': XCOPY_wprintf(XCOPY_LoadMessage(STRING_HELP));
+                          rc = RC_HELP;
+                          goto out;
+                case 'V':
+                    WINE_FIXME("ignoring /V\n");
+                    break;
+                default:
+                    WINE_TRACE("Unhandled parameter '%s'\n", wine_dbgstr_w(word));
+                    XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARM), word);
+                    goto out;
+                }
+
+                /* Unless overriden above, skip over the '/' and the first character */
+                if (rest == NULL) rest = &word[2];
+
+                /* By now, rest should point either to the null after the
+                   switch, or the beginning of the next switch if there
+                   was no whitespace between them                          */
+                if (!skip && *rest && *rest != '/') {
+                    WINE_FIXME("Unexpected characters found and ignored '%s'\n", wine_dbgstr_w(rest));
+                    skip=1;
+                } else {
+                    word = rest;
+                }
             }
         }
         word = next;
-- 
2.17.1




More information about the wine-devel mailing list