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@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;