cmd: Add full for /R support.

This commit is contained in:
Jason Edmeades 2012-09-27 19:58:56 +01:00 committed by Alexandre Julliard
parent 8fbd65358e
commit 9dde62cb96
3 changed files with 416 additions and 185 deletions

View File

@ -1079,10 +1079,13 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
WCHAR variable[4]; WCHAR variable[4];
WCHAR *firstCmd; WCHAR *firstCmd;
int thisDepth; int thisDepth;
WCHAR optionsRoot[MAX_PATH];
DIRECTORY_STACK *dirsToWalk = NULL;
BOOL expandDirs = FALSE; BOOL expandDirs = FALSE;
BOOL useNumbers = FALSE; BOOL useNumbers = FALSE;
BOOL doFileset = FALSE; BOOL doFileset = FALSE;
BOOL doRecurse = FALSE;
BOOL doExecuted = FALSE; /* Has the 'do' part been executed */ BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */ LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
int itemNum; int itemNum;
@ -1091,6 +1094,8 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
/* Handle optional qualifiers (multiple are allowed) */ /* Handle optional qualifiers (multiple are allowed) */
WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, NULL, FALSE); WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, NULL, FALSE);
optionsRoot[0] = 0;
while (thisArg && *thisArg == '/') { while (thisArg && *thisArg == '/') {
WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg)); WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
thisArg++; thisArg++;
@ -1103,20 +1108,23 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
case 'R': case 'R':
case 'F': case 'F':
{ {
BOOL isRecursive = (*thisArg == 'R'); /* When recursing directories, use current directory as the starting point unless
subsequently overridden */
doRecurse = (toupperW(*thisArg) == 'R');
if (doRecurse) GetCurrentDirectoryW(sizeof(optionsRoot)/sizeof(WCHAR), optionsRoot);
if (!isRecursive) doFileset = (toupperW(*thisArg) == 'F');
doFileset = TRUE;
/* Retrieve next parameter to see if is path/options */ /* Retrieve next parameter to see if is root/options (raw form required
thisArg = WCMD_parameter(p, parameterNo, NULL, NULL, !isRecursive); with for /f, or unquoted in for /r) */
thisArg = WCMD_parameter(p, parameterNo, NULL, NULL, doFileset);
/* Next parm is either qualifier, path/options or variable - /* Next parm is either qualifier, path/options or variable -
only care about it if it is the path/options */ only care about it if it is the path/options */
if (thisArg && *thisArg != '/' && *thisArg != '%') { if (thisArg && *thisArg != '/' && *thisArg != '%') {
parameterNo++; parameterNo++;
if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n"); strcpyW(optionsRoot, thisArg);
else { if (!doRecurse) {
static unsigned int once; static unsigned int once;
if (!once++) WINE_FIXME("/F needs to handle options\n"); if (!once++) WINE_FIXME("/F needs to handle options\n");
} }
@ -1137,6 +1145,17 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
return; return;
} }
/* Set up the list of directories to recurse if we are going to */
if (doRecurse) {
/* Allocate memory, add to list */
dirsToWalk = HeapAlloc(GetProcessHeap(), 0, sizeof(DIRECTORY_STACK));
dirsToWalk->next = NULL;
dirsToWalk->dirName = HeapAlloc(GetProcessHeap(),0,
(strlenW(optionsRoot) + 1) * sizeof(WCHAR));
strcpyW(dirsToWalk->dirName, optionsRoot);
WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
}
/* Variable should follow */ /* Variable should follow */
strcpyW(variable, thisArg); strcpyW(variable, thisArg);
WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable)); WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
@ -1178,13 +1197,58 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
return; return;
} }
/* Save away the starting position for the commands (and offset for the
first one */
cmdStart = *cmdList;
cmdEnd = *cmdList; cmdEnd = *cmdList;
/* Loop repeatedly per-directory we are potentially walking, when in for /r
mode, or once for the rest of the time. */
do {
WCHAR fullitem[MAX_PATH];
static const WCHAR slashstarW[] = {'\\','*','\0'};
/* Save away the starting position for the commands (and offset for the
first one) */
cmdStart = *cmdList;
firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */ firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
itemNum = 0; itemNum = 0;
/* If we are recursing directories (ie /R), add all sub directories now, then
prefix the root when searching for the item */
if (dirsToWalk) {
DIRECTORY_STACK *remainingDirs = dirsToWalk;
/* Build a generic search and add all directories on the list of directories
still to walk */
strcpyW(fullitem, dirsToWalk->dirName);
strcatW(fullitem, slashstarW);
hff = FindFirstFileW(fullitem, &fd);
if (hff != INVALID_HANDLE_VALUE) {
do {
WINE_TRACE("Looking for subdirectories\n");
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
(strcmpW(fd.cFileName, dotdotW) != 0) &&
(strcmpW(fd.cFileName, dotW) != 0))
{
/* Allocate memory, add to list */
DIRECTORY_STACK *toWalk = HeapAlloc(GetProcessHeap(), 0, sizeof(DIRECTORY_STACK));
WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
toWalk->next = remainingDirs->next;
remainingDirs->next = toWalk;
remainingDirs = toWalk;
toWalk->dirName = HeapAlloc(GetProcessHeap(), 0,
sizeof(WCHAR) *
(strlenW(dirsToWalk->dirName) + 2 + strlenW(fd.cFileName)));
strcpyW(toWalk->dirName, dirsToWalk->dirName);
strcatW(toWalk->dirName, slashW);
strcatW(toWalk->dirName, fd.cFileName);
WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
toWalk, toWalk->next);
}
} while (FindNextFileW(hff, &fd) != 0);
WINE_TRACE("Finished adding all subdirectories\n");
FindClose (hff);
}
}
thisSet = setStart; thisSet = setStart;
/* Loop through all set entries */ /* Loop through all set entries */
while (thisSet && while (thisSet &&
@ -1210,8 +1274,21 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item)); WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
if (!useNumbers && !doFileset) { if (!useNumbers && !doFileset) {
if (strpbrkW (item, wildcards)) { WCHAR fullitem[MAX_PATH];
hff = FindFirstFileW(item, &fd);
/* Now build the item to use / search for in the specified directory,
as it is fully qualified in the /R case */
if (dirsToWalk) {
strcpyW(fullitem, dirsToWalk->dirName);
strcatW(fullitem, slashW);
strcatW(fullitem, item);
} else {
strcpyW(fullitem, item);
}
if (strpbrkW (fullitem, wildcards)) {
hff = FindFirstFileW(fullitem, &fd);
if (hff != INVALID_HANDLE_VALUE) { if (hff != INVALID_HANDLE_VALUE) {
do { do {
BOOL isDirectory = FALSE; BOOL isDirectory = FALSE;
@ -1225,17 +1302,26 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
{ {
thisCmdStart = cmdStart; thisCmdStart = cmdStart;
WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName)); WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
if (doRecurse) {
strcpyW(fullitem, dirsToWalk->dirName);
strcatW(fullitem, slashW);
strcatW(fullitem, fd.cFileName);
} else {
strcpyW(fullitem, fd.cFileName);
}
doExecuted = TRUE; doExecuted = TRUE;
WCMD_part_execute (&thisCmdStart, firstCmd, variable, WCMD_part_execute (&thisCmdStart, firstCmd, variable,
fd.cFileName, FALSE, TRUE); fullitem, FALSE, TRUE);
cmdEnd = thisCmdStart;
} }
} while (FindNextFileW(hff, &fd) != 0); } while (FindNextFileW(hff, &fd) != 0);
FindClose (hff); FindClose (hff);
} }
} else { } else {
doExecuted = TRUE; doExecuted = TRUE;
WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE); WCMD_part_execute(&thisCmdStart, firstCmd, variable, fullitem, FALSE, TRUE);
cmdEnd = thisCmdStart;
} }
} else if (useNumbers) { } else if (useNumbers) {
@ -1290,7 +1376,7 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
} else { } else {
WCHAR buffer[MAXSTRING] = {'\0'}; WCHAR buffer[MAXSTRING];
WCHAR *where, *parm; WCHAR *where, *parm;
while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) { while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
@ -1309,7 +1395,7 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
cmdEnd = thisCmdStart; cmdEnd = thisCmdStart;
} }
buffer[0] = 0x00; buffer[0] = 0;
} }
CloseHandle (input); CloseHandle (input);
@ -1322,7 +1408,7 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
/* Filesets - A string literal */ /* Filesets - A string literal */
} else if (doFileset && *itemStart == '"') { } else if (doFileset && *itemStart == '"') {
WCHAR buffer[MAXSTRING] = {'\0'}; WCHAR buffer[MAXSTRING];
WCHAR *where, *parm; WCHAR *where, *parm;
/* Skip blank lines, and re-extract parameter now string has quotes removed */ /* Skip blank lines, and re-extract parameter now string has quotes removed */
@ -1342,7 +1428,6 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
} }
WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd); WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
cmdEnd = thisCmdStart;
i++; i++;
} }
@ -1371,6 +1456,19 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
cmdEnd = thisCmdStart; cmdEnd = thisCmdStart;
} }
/* If we are walking directories, move on to any which remain */
if (dirsToWalk != NULL) {
DIRECTORY_STACK *nextDir = dirsToWalk->next;
HeapFree(GetProcessHeap(), 0, dirsToWalk->dirName);
HeapFree(GetProcessHeap(), 0, dirsToWalk);
dirsToWalk = nextDir;
if (dirsToWalk) WINE_TRACE("Moving to next directorty to iterate: %s\n",
wine_dbgstr_w(dirsToWalk->dirName));
else WINE_TRACE("Finished all directories.\n");
}
} while (dirsToWalk != NULL);
/* Now skip over the do part if we did not perform the for loop so far. /* Now skip over the do part if we did not perform the for loop so far.
We store in cmdEnd the next command after the do block, but we only We store in cmdEnd the next command after the do block, but we only
know this if something was run. If it has not been, we need to calculate know this if something was run. If it has not been, we need to calculate

View File

@ -726,6 +726,129 @@ rem del tmp
rem for /d %%i in (*) do echo %%i>> tmp rem for /d %%i in (*) do echo %%i>> tmp
rem sort < tmp rem sort < tmp
rem del tmp rem del tmp
echo > baz\bazbaz
goto :TestForR
:SetExpected
del temp.bat 2>nul
call :WriteLine set found=N
for /l %%i in (1,1,%expectedresults%) do (
call :WriteLine if "%%%%expectedresults.%%i%%%%"=="%%%%1" set found=Y
call :WriteLine if "%%%%found%%%%"=="Y" set expectedresults.%%i=
call :WriteLine if "%%%%found%%%%"=="Y" goto :eof
)
call :WriteLine echo Got unexpected result: "%%%%1"
goto :eof
:WriteLine
echo %*>> temp.bat
goto :EOF
:ValidateExpected
del temp.bat 2>nul
for /l %%i in (1,1,%expectedresults%) do call :WriteLine if not "%%%%expectedresults.%%i%%%%"=="" echo Found missing result: "%%%%expectedresults.%%i%%%%"
call temp.bat
del temp.bat 2>nul
goto :eof
:TestForR
rem %CD% does not tork on NT4 so use the following workaround
for /d %%i in (.) do set CURDIR=%%~dpnxi
echo --- for /R
echo Plain directory enumeration
set expectedresults=4
set expectedresults.1=%CURDIR%\.
set expectedresults.2=%CURDIR%\bar\.
set expectedresults.3=%CURDIR%\baz\.
set expectedresults.4=%CURDIR%\foo\.
call :SetExpected
for /R %%i in (.) do call temp.bat %%i
call :ValidateExpected
echo Plain directory enumeration from provided root
set expectedresults=4
set expectedresults.1=%CURDIR%\.
set expectedresults.2=%CURDIR%\bar\.
set expectedresults.3=%CURDIR%\baz\.
set expectedresults.4=%CURDIR%\foo\.
if "%CD%"=="" goto :SkipBrokenNT4
call :SetExpected
for /R "%CURDIR%" %%i in (.) do call temp.bat %%i
call :ValidateExpected
:SkipBrokenNT4
echo File enumeration
set expectedresults=2
set expectedresults.1=%CURDIR%\baz\bazbaz
set expectedresults.2=%CURDIR%\bazbaz
call :SetExpected
for /R %%i in (baz*) do call temp.bat %%i
call :ValidateExpected
echo File enumeration from provided root
set expectedresults=2
set expectedresults.1=%CURDIR%\baz\bazbaz
set expectedresults.2=%CURDIR%\bazbaz
call :SetExpected
for /R %%i in (baz*) do call temp.bat %%i
call :ValidateExpected
echo Mixed enumeration
set expectedresults=6
set expectedresults.1=%CURDIR%\.
set expectedresults.2=%CURDIR%\bar\.
set expectedresults.3=%CURDIR%\baz\.
set expectedresults.4=%CURDIR%\baz\bazbaz
set expectedresults.5=%CURDIR%\bazbaz
set expectedresults.6=%CURDIR%\foo\.
call :SetExpected
for /R %%i in (. baz*) do call temp.bat %%i
call :ValidateExpected
echo Mixed enumeration from provided root
set expectedresults=6
set expectedresults.1=%CURDIR%\.
set expectedresults.2=%CURDIR%\bar\.
set expectedresults.3=%CURDIR%\baz\.
set expectedresults.4=%CURDIR%\baz\bazbaz
set expectedresults.5=%CURDIR%\bazbaz
set expectedresults.6=%CURDIR%\foo\.
call :SetExpected
for /R %%i in (. baz*) do call temp.bat %%i
call :ValidateExpected
echo With duplicates enumeration
set expectedresults=12
set expectedresults.1=%CURDIR%\bar\bazbaz
set expectedresults.2=%CURDIR%\bar\fred
set expectedresults.3=%CURDIR%\baz\bazbaz
set expectedresults.4=%CURDIR%\baz\bazbaz
set expectedresults.5=%CURDIR%\baz\bazbaz
set expectedresults.6=%CURDIR%\baz\fred
set expectedresults.7=%CURDIR%\bazbaz
set expectedresults.8=%CURDIR%\bazbaz
set expectedresults.9=%CURDIR%\bazbaz
set expectedresults.10=%CURDIR%\foo\bazbaz
set expectedresults.11=%CURDIR%\foo\fred
set expectedresults.12=%CURDIR%\fred
call :SetExpected
for /R %%i in (baz* bazbaz fred ba*) do call temp.bat %%i
call :ValidateExpected
echo Strip missing wildcards, keep unwildcarded names
set expectedresults=6
set expectedresults.1=%CURDIR%\bar\jim
set expectedresults.2=%CURDIR%\baz\bazbaz
set expectedresults.3=%CURDIR%\baz\jim
set expectedresults.4=%CURDIR%\bazbaz
set expectedresults.5=%CURDIR%\foo\jim
set expectedresults.6=%CURDIR%\jim
call :SetExpected
for /R %%i in (baz* fred* jim) do call temp.bat %%i
call :ValidateExpected
echo for /R passed
cd .. & rd /s/Q foobar cd .. & rd /s/Q foobar
echo --- for /L echo --- for /L
rem Some cases loop forever writing 0s, like e.g. (1,0,1), (1,a,3) or (a,b,c); those can't be tested here rem Some cases loop forever writing 0s, like e.g. (1,0,1), (1,a,3) or (a,b,c); those can't be tested here

View File

@ -495,6 +495,16 @@ bar@space@
PASSED PASSED
xxx - Should be xxx xxx - Should be xxx
Expected second line Expected second line
--- for /R
Plain directory enumeration
Plain directory enumeration from provided root
File enumeration
File enumeration from provided root
Mixed enumeration
Mixed enumeration from provided root
With duplicates enumeration
Strip missing wildcards, keep unwildcarded names
for /R passed
--- for /L --- for /L
1 1
3 3