xcopy: Handle multiple switches concatenated without whitespace.
Signed-off-by: Jason Edmeades <us@edmeades.me.uk> Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
parent
4c13e1765f
commit
7396f32920
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
int skip=0;
|
||||
WCHAR *rest;
|
||||
|
||||
/* 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;
|
||||
while (word[0]) {
|
||||
rest = NULL;
|
||||
|
||||
/* 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));
|
||||
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;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
if (!isError) {
|
||||
st.wYear = _wtol(pos);
|
||||
while (*pos && is_digit(*pos)) pos++;
|
||||
if (st.wYear < 100) st.wYear+=2000;
|
||||
}
|
||||
|
||||
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 {
|
||||
/* 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_DATENEWER;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
flags |= OPT_EXCLUDELIST;
|
||||
|
||||
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;
|
||||
/* Do not support concatenated switches onto exclude lists yet */
|
||||
rest = end;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
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 {
|
||||
flags |= OPT_DATENEWER;
|
||||
}
|
||||
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;
|
||||
|
|
Loading…
Reference in New Issue