Fixes todo in tests
When 's' is used as a modifier, the paths that are presented to the other modifiers needs to be a short path. Given the 'filename' part of the path may not exist, we cannot use GetShortPathName directly without first removing the filename part to just leave the directory bit.
Signed-off-by: Jason Edmeades us@edmeades.me.uk --- programs/cmd/batch.c | 17 +++++++++++++---- programs/cmd/tests/test_builtins.cmd.exp | 12 ++++++------ 2 files changed, 19 insertions(+), 10 deletions(-)
diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c index 1a78b55557..e10baf852c 100644 --- a/programs/cmd/batch.c +++ b/programs/cmd/batch.c @@ -399,6 +399,7 @@ void WCMD_HandleTildaModifiers(WCHAR **start, BOOL atExecute) WCHAR finaloutput[MAX_PATH]; WCHAR fullfilename[MAX_PATH]; WCHAR thisoutput[MAX_PATH]; + WCHAR *filepart = NULL; WCHAR *pos = *start+1; WCHAR *firstModifier = pos; WCHAR *lastModifier = NULL; @@ -522,7 +523,7 @@ void WCMD_HandleTildaModifiers(WCHAR **start, BOOL atExecute) /* After this, we need full information on the file, which is valid not to exist. */ if (!skipFileParsing) { - if (GetFullPathNameW(outputparam, MAX_PATH, fullfilename, NULL) == 0) { + if (GetFullPathNameW(outputparam, MAX_PATH, fullfilename, &filepart) == 0) { exists = FALSE; fullfilename[0] = 0x00; } else { @@ -598,8 +599,16 @@ void WCMD_HandleTildaModifiers(WCHAR **start, BOOL atExecute) /* 4. Handle 's' : Use short paths (File doesn't have to exist) */ if (memchrW(firstModifier, 's', modifierLen) != NULL) { if (finaloutput[0] != 0x00) strcatW(finaloutput, spaceW); - /* Don't flag as doneModifier - %~s on its own is processed later */ - GetShortPathNameW(outputparam, outputparam, sizeof(outputparam)/sizeof(outputparam[0])); + + /* Convert fullfilename's path to a short path - Save filename away as + only path is valid, name may not exist which causes GetShortPathName + to fail if it is provided */ + if (filepart) { + strcpyW(thisoutput, filepart); + *filepart = 0x00; + GetShortPathNameW(fullfilename, fullfilename, sizeof(fullfilename)/sizeof(fullfilename[0])); + strcatW(fullfilename, thisoutput); + } }
/* 5. Handle 'f' : Fully qualified path (File doesn't have to exist) */ @@ -673,7 +682,7 @@ void WCMD_HandleTildaModifiers(WCHAR **start, BOOL atExecute) memchrW(firstModifier, 's', modifierLen) != NULL) { doneModifier = TRUE; if (finaloutput[0] != 0x00) strcatW(finaloutput, spaceW); - strcatW(finaloutput, outputparam); + strcatW(finaloutput, fullfilename); } } } diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 3118359265..b96d38ff0a 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -527,9 +527,9 @@ N '.OOL' '.TABC' '' -@todo_wine@'@drive@@shortpath@R S'@or_broken@'' -@todo_wine@'@drive@@shortpath@T'@or_broken@'' -@todo_wine@'@drive@@shortpath@ABCDEFGHIJK.LMNOP'@or_broken@'' +'@drive@@shortpath@R S'@or_broken@'' +'@drive@@shortpath@T'@or_broken@'' +'@drive@@shortpath@ABCDEFGHIJK.LMNOP'@or_broken@'' ''@or_broken@'%~ai' ''@or_broken@'%~ai' '--a------'@or_broken@'--a--------'@or_broken@'--a--c---'@or_broken@'%~ai' @@ -563,9 +563,9 @@ N '.OOL' '.TABC' '' -@todo_wine@'@drive@@shortpath@R S'@or_broken@'' -@todo_wine@'@drive@@shortpath@T'@or_broken@'' -@todo_wine@'@drive@@shortpath@ABCDEFGHIJK.LMNOP'@or_broken@'' +'@drive@@shortpath@R S'@or_broken@'' +'@drive@@shortpath@T'@or_broken@'' +'@drive@@shortpath@ABCDEFGHIJK.LMNOP'@or_broken@'' @drive@@path@ @drive@@path@ @drive@
Fixes bugs#28037 and #41914 and a few todo's in the tests
A single line if statement causes problems when it has redirects and/or continuation type operators (|, &&, || etc) because it is expected that if there is more than one command in the 'if', then it will use brackets. This patch changes the 'if' parsing to emulate brackets at a continuation character. In addition, 'for' and 'if' statements do not have their output redirected immediately, instead it is redirected on the individual commands being executed not the statement itself. We were opening the redirect once for the 'if' and once for the processing of the statement inside the if.
A few points to note...
The patch is larger than I would have liked because I had to shuffle the WCMD_execute routine a little, which hit problems with (uselessly named) variable reuse, and I also had to add a condition around the redirects processing to avoid doing it for an 'if' or 'for' statement.
There are still some more issues remaining, which will be easier to progress when Bernhard Übelacker's patch for bug#44338 is committed, when we will know the end of the 'if' condition.
Signed-off-by: Jason Edmeades us@edmeades.me.uk --- programs/cmd/builtins.c | 16 +- programs/cmd/tests/test_builtins.cmd | 9 + programs/cmd/tests/test_builtins.cmd.exp | 7 +- programs/cmd/wcmdmain.c | 309 +++++++++++++---------- 4 files changed, 200 insertions(+), 141 deletions(-)
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index 088632f214..f2a92c1a20 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -1544,8 +1544,8 @@ static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd, CMD_LIST *curPosition = *cmdList; int myDepth = (*cmdList)->bracketDepth;
- WINE_TRACE("cmdList(%p), firstCmd(%s), doIt(%d)\n", cmdList, wine_dbgstr_w(firstcmd), - executecmds); + WINE_TRACE("cmdList(%p), firstCmd(%s), doIt(%d), isIF(%d)\n", cmdList, + wine_dbgstr_w(firstcmd), executecmds, isIF);
/* Skip leading whitespace between condition and the command */ while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++; @@ -1592,7 +1592,8 @@ static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd, } else if ((*cmdList)->bracketDepth > myDepth) { if (processThese) { *cmdList = WCMD_process_commands(*cmdList, TRUE, FALSE); - WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList); + } else { + WINE_TRACE("Skipping command %p due to stack depth\n", *cmdList); } if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
@@ -1629,9 +1630,16 @@ static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd, processThese = TRUE; } if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand; + + /* If we were in an IF statement and we didnt find an else and yet we get back to + the same bracket depth as the IF, then the IF statement is over. This is required + to handle nested ifs properly */ + } else if (isIF && (*cmdList)->bracketDepth == myDepth) { + WINE_TRACE("Found end of this nested IF statement, ending this if\n"); + break; } else if (!processThese) { if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand; - WINE_TRACE("Ignore the next command as well (next = %p)\n", *cmdList); + WINE_TRACE("Skipping this command, as in not process mode (next = %p)\n", *cmdList); } else { WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList); break; diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd index b9e9b259a9..d794805b71 100644 --- a/programs/cmd/tests/test_builtins.cmd +++ b/programs/cmd/tests/test_builtins.cmd @@ -179,6 +179,8 @@ if exist foo (type foo) else echo not supported echo --- redirections within IF statements if 1==1 echo foo1>bar type bar & del bar +if 1==1 echo foo2>>bar +type bar & del bar echo ----- if 1==1 (echo foo2>bar) else echo baz2>bar type bar & del bar @@ -908,6 +910,13 @@ if %elseIF% == 1 ( ) else ( echo else if seems to be broken ) +if "x" == "a" ( + echo broken1 +) else ( + echo expected1 + if "y" == "b" echo broken2 + echo expected post-embedded if +) echo --- case sensitivity with and without /i option if bar==BAR echo if does not default to case sensitivity if not bar==BAR echo if seems to default to case sensitivity diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index b96d38ff0a..9e77e98c25 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -205,7 +205,8 @@ food21 @todo_wine@foo7@space@@space@@or_broken@not supported@space@ @todo_wine@foo@or_broken@not supported --- redirections within IF statements -@todo_wine@foo1 +foo1 +foo2 ----- foo2 foo3 @@ -430,7 +431,7 @@ p1 q1 @todo_wine@--- --- chain else (if false) -@todo_wine@j3 +j3 --- k3 l3 @@ -658,6 +659,8 @@ if seems not to detect /c as parameter else if seems to work else if seems to work else if seems to work +expected1 +expected post-embedded if --- case sensitivity with and without /i option if seems to default to case sensitivity if /i seems to work diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 8fe2d574e5..01e650a86c 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -1262,8 +1262,9 @@ void WCMD_run_program (WCHAR *command, BOOL called) void WCMD_execute (const WCHAR *command, const WCHAR *redirects, CMD_LIST **cmdList, BOOL retrycall) { - WCHAR *cmd, *p, *redir; - int status, i; + WCHAR *cmd, *parms_start, *redir; + WCHAR *pos; + int status, i, cmd_index; DWORD count, creationDisposition; HANDLE h; WCHAR *whichcmd; @@ -1281,13 +1282,42 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects, WINE_TRACE("command on entry:%s (%p)\n", wine_dbgstr_w(command), cmdList);
+ /* Move copy of the command onto the heap so it can be expanded */ + new_cmd = heap_alloc(MAXSTRING * sizeof(WCHAR)); + strcpyW(new_cmd, command); + cmd = new_cmd; + + /* Move copy of the redirects onto the heap so it can be expanded */ + new_redir = heap_alloc(MAXSTRING * sizeof(WCHAR)); + redir = new_redir; + + /* Strip leading whitespaces, and a '@' if supplied */ + whichcmd = WCMD_skip_leading_spaces(cmd); + WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd)); + if (whichcmd[0] == '@') whichcmd++; + + /* Check if the command entered is internal, and identify which one */ + count = 0; + while (IsCharAlphaNumericW(whichcmd[count])) { + count++; + } + for (i=0; i<=WCMD_EXIT; i++) { + if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, + whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break; + } + cmd_index = i; + parms_start = WCMD_skip_leading_spaces (&whichcmd[count]); + /* If the next command is a pipe then we implement pipes by redirecting the output from this command to a temp file and input into the next command from that temp file. + Note: Do not do this for a for or if statement as the pipe is for + the individual statements, not the for or if itself. FIXME: Use of named pipes would make more sense here as currently this process has to finish before the next one can start but this requires a change to not wait for the first app to finish but rather the pipe */ - if (cmdList && (*cmdList)->nextcommand && + if (!(cmd_index == WCMD_FOR || cmd_index == WCMD_IF) && + cmdList && (*cmdList)->nextcommand && (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
WCHAR temp_path[MAX_PATH]; @@ -1304,13 +1334,6 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects, wine_dbgstr_w((*cmdList)->nextcommand->pipeFile)); }
- /* Move copy of the command onto the heap so it can be expanded */ - new_cmd = heap_alloc(MAXSTRING * sizeof(WCHAR)); - strcpyW(new_cmd, command); - - /* Move copy of the redirects onto the heap so it can be expanded */ - new_redir = heap_alloc(MAXSTRING * sizeof(WCHAR)); - /* If piped output, send stdout to the pipe by appending >filename to redirects */ if (piped) { static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'}; @@ -1324,7 +1347,6 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects, be expanded as the line is read in, except for 'for' loops) */ handleExpansion(new_cmd, (context != NULL), delayedsubst); handleExpansion(new_redir, (context != NULL), delayedsubst); - cmd = new_cmd;
/* * Changing default drive has to be handled as a special case, anything @@ -1361,16 +1383,33 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects, sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE;
-/* - * Redirect stdin, stdout and/or stderr if required. - */ + /* + * Redirect stdin, stdout and/or stderr if required. + * Note: Do not do this for a for or if statement as the pipe is for + * the individual statements, not the for or if itself. + */ + if (!(cmd_index == WCMD_FOR || cmd_index == WCMD_IF)) { + /* STDIN could come from a preceding pipe, so delete on close if it does */ + if (cmdList && (*cmdList)->pipeFile[0] != 0x00) { + WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile)); + h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL); + if (h == INVALID_HANDLE_VALUE) { + WCMD_print_error (); + heap_free(cmd); + heap_free(new_redir); + return; + } + SetStdHandle (STD_INPUT_HANDLE, h); + + /* No need to remember the temporary name any longer once opened */ + (*cmdList)->pipeFile[0] = 0x00;
- /* STDIN could come from a preceding pipe, so delete on close if it does */ - if (cmdList && (*cmdList)->pipeFile[0] != 0x00) { - WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile)); - h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL); + /* Otherwise STDIN could come from a '<' redirect */ + } else if ((pos = strchrW(new_redir,'<')) != NULL) { + h = CreateFileW(WCMD_parameter(++pos, 0, NULL, FALSE, FALSE), GENERIC_READ, FILE_SHARE_READ, + &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (h == INVALID_HANDLE_VALUE) { WCMD_print_error (); heap_free(cmd); @@ -1378,122 +1417,87 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects, return; } SetStdHandle (STD_INPUT_HANDLE, h); - - /* No need to remember the temporary name any longer once opened */ - (*cmdList)->pipeFile[0] = 0x00; - - /* Otherwise STDIN could come from a '<' redirect */ - } else if ((p = strchrW(new_redir,'<')) != NULL) { - h = CreateFileW(WCMD_parameter(++p, 0, NULL, FALSE, FALSE), GENERIC_READ, FILE_SHARE_READ, - &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (h == INVALID_HANDLE_VALUE) { - WCMD_print_error (); - heap_free(cmd); - heap_free(new_redir); - return; } - SetStdHandle (STD_INPUT_HANDLE, h); - } - - /* Scan the whole command looking for > and 2> */ - redir = new_redir; - while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) { - int handle = 0;
- if (p > redir && (*(p-1)=='2')) - handle = 2; - else - handle = 1; + /* Scan the whole command looking for > and 2> */ + while (redir != NULL && ((pos = strchrW(redir,'>')) != NULL)) { + int handle = 0;
- p++; - if ('>' == *p) { - creationDisposition = OPEN_ALWAYS; - p++; - } - else { - creationDisposition = CREATE_ALWAYS; - } + if (pos > redir && (*(pos-1)=='2')) + handle = 2; + else + handle = 1;
- /* Add support for 2>&1 */ - redir = p; - if (*p == '&') { - int idx = *(p+1) - '0'; - - if (DuplicateHandle(GetCurrentProcess(), - GetStdHandle(idx_stdhandles[idx]), - GetCurrentProcess(), - &h, - 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) { - WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError()); + pos++; + if ('>' == *pos) { + creationDisposition = OPEN_ALWAYS; + pos++; } - WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h); - - } else { - WCHAR *param = WCMD_parameter(p, 0, NULL, FALSE, FALSE); - h = CreateFileW(param, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, - &sa, creationDisposition, FILE_ATTRIBUTE_NORMAL, NULL); - if (h == INVALID_HANDLE_VALUE) { - WCMD_print_error (); - heap_free(cmd); - heap_free(new_redir); - return; - } - if (SetFilePointer (h, 0, NULL, FILE_END) == - INVALID_SET_FILE_POINTER) { - WCMD_print_error (); + else { + creationDisposition = CREATE_ALWAYS; } - WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h); - } - - SetStdHandle (idx_stdhandles[handle], h); - }
-/* - * Strip leading whitespaces, and a '@' if supplied - */ - whichcmd = WCMD_skip_leading_spaces(cmd); - WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd)); - if (whichcmd[0] == '@') whichcmd++; + /* Add support for 2>&1 */ + redir = pos; + if (*pos == '&') { + int idx = *(pos+1) - '0'; + + if (DuplicateHandle(GetCurrentProcess(), + GetStdHandle(idx_stdhandles[idx]), + GetCurrentProcess(), + &h, + 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) { + WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError()); + } + WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
-/* - * Check if the command entered is internal. If it is, pass the rest of the - * line down to the command. If not try to run a program. - */ + } else { + WCHAR *param = WCMD_parameter(pos, 0, NULL, FALSE, FALSE); + h = CreateFileW(param, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, + &sa, creationDisposition, FILE_ATTRIBUTE_NORMAL, NULL); + if (h == INVALID_HANDLE_VALUE) { + WCMD_print_error (); + heap_free(cmd); + heap_free(new_redir); + return; + } + if (SetFilePointer (h, 0, NULL, FILE_END) == + INVALID_SET_FILE_POINTER) { + WCMD_print_error (); + } + WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h); + }
- count = 0; - while (IsCharAlphaNumericW(whichcmd[count])) { - count++; - } - for (i=0; i<=WCMD_EXIT; i++) { - if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, - whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break; + SetStdHandle (idx_stdhandles[handle], h); + } + } else { + WINE_TRACE("Not touching redirects for a FOR or IF command\n"); } - p = WCMD_skip_leading_spaces (&whichcmd[count]); - WCMD_parse (p, quals, param1, param2); + WCMD_parse (parms_start, quals, param1, param2); WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
- if (i <= WCMD_EXIT && (p[0] == '/') && (p[1] == '?')) { + if (i <= WCMD_EXIT && (parms_start[0] == '/') && (parms_start[1] == '?')) { /* this is a help request for a builtin program */ i = WCMD_HELP; - memcpy(p, whichcmd, count * sizeof(WCHAR)); - p[count] = '\0'; + memcpy(parms_start, whichcmd, count * sizeof(WCHAR)); + parms_start[count] = '\0';
}
switch (i) {
case WCMD_CALL: - WCMD_call (p); + WCMD_call (parms_start); break; case WCMD_CD: case WCMD_CHDIR: - WCMD_setshow_default (p); + WCMD_setshow_default (parms_start); break; case WCMD_CLS: WCMD_clear_screen (); break; case WCMD_COPY: - WCMD_copy (p); + WCMD_copy (parms_start); break; case WCMD_CTTY: WCMD_change_tty (); @@ -1503,10 +1507,10 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects, break; case WCMD_DEL: case WCMD_ERASE: - WCMD_delete (p); + WCMD_delete (parms_start); break; case WCMD_DIR: - WCMD_directory (p); + WCMD_directory (parms_start); break; case WCMD_ECHO: WCMD_echo(&whichcmd[count]); @@ -1515,20 +1519,20 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects, WCMD_goto (cmdList); break; case WCMD_HELP: - WCMD_give_help (p); - break; + WCMD_give_help (parms_start); + break; case WCMD_LABEL: - WCMD_volume (TRUE, p); + WCMD_volume (TRUE, parms_start); break; case WCMD_MD: case WCMD_MKDIR: - WCMD_create_dir (p); - break; + WCMD_create_dir (parms_start); + break; case WCMD_MOVE: WCMD_move (); break; case WCMD_PATH: - WCMD_setshow_path (p); + WCMD_setshow_path (parms_start); break; case WCMD_PAUSE: WCMD_pause (); @@ -1544,22 +1548,22 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects, break; case WCMD_RD: case WCMD_RMDIR: - WCMD_remove_dir (p); + WCMD_remove_dir (parms_start); break; case WCMD_SETLOCAL: - WCMD_setlocal(p); + WCMD_setlocal(parms_start); break; case WCMD_ENDLOCAL: WCMD_endlocal(); break; case WCMD_SET: - WCMD_setshow_env (p); - break; + WCMD_setshow_env (parms_start); + break; case WCMD_SHIFT: - WCMD_shift (p); + WCMD_shift (parms_start); break; case WCMD_START: - WCMD_start (p); + WCMD_start (parms_start); break; case WCMD_TIME: WCMD_setshow_time (); @@ -1569,41 +1573,41 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects, WCMD_title(&whichcmd[count+1]); break; case WCMD_TYPE: - WCMD_type (p); - break; + WCMD_type (parms_start); + break; case WCMD_VER: WCMD_output_asis(newlineW); WCMD_version (); break; case WCMD_VERIFY: - WCMD_verify (p); + WCMD_verify (parms_start); break; case WCMD_VOL: - WCMD_volume (FALSE, p); + WCMD_volume (FALSE, parms_start); break; case WCMD_PUSHD: - WCMD_pushd(p); + WCMD_pushd(parms_start); break; case WCMD_POPD: WCMD_popd(); break; case WCMD_ASSOC: - WCMD_assoc(p, TRUE); + WCMD_assoc(parms_start, TRUE); break; case WCMD_COLOR: WCMD_color(); break; case WCMD_FTYPE: - WCMD_assoc(p, FALSE); + WCMD_assoc(parms_start, FALSE); break; case WCMD_MORE: - WCMD_more(p); + WCMD_more(parms_start); break; case WCMD_CHOICE: - WCMD_choice(p); + WCMD_choice(parms_start); break; case WCMD_MKLINK: - WCMD_mklink(p); + WCMD_mklink(parms_start); break; case WCMD_EXIT: WCMD_exit (cmdList); @@ -1614,8 +1618,8 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects, these two commands, neither 'for' nor 'if' is supported when called, i.e. 'call if 1==1...' will fail. */ if (!retrycall) { - if (i==WCMD_FOR) WCMD_for (p, cmdList); - else if (i==WCMD_IF) WCMD_if (p, cmdList); + if (i==WCMD_FOR) WCMD_for (parms_start, cmdList); + else if (i==WCMD_IF) WCMD_if (parms_start, cmdList); break; } /* else: drop through */ @@ -1823,6 +1827,8 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE BOOL lastWasElse = FALSE; BOOL lastWasRedirect = TRUE; BOOL lastWasCaret = FALSE; + int lineCurDepth; /* Bracket depth when line was read in */ + BOOL resetAtEndOfLine = FALSE; /* Do we need to reset curdepth at EOL */
/* Allocate working space for a command read from keyboard, file etc */ if (!extraSpace) @@ -1890,6 +1896,7 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE curCopyTo = curString; curLen = &curStringLen; lastWasRedirect = FALSE; /* Required e.g. for spaces between > and filename */ + lineCurDepth = curDepth; /* What was the curdepth at the beginning of the line */
/* Parse every character on the line being processed */ while (*curPos != 0x00) { @@ -1936,6 +1943,15 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR)); (*curLen)+=keyw_len; curPos+=keyw_len; + + /* If we had a single line if XXX which reaches an else (needs odd + syntax like if 1=1 command && (command) else command we pretended + to add brackets for the if, so they are now over */ + if (resetAtEndOfLine) { + WINE_TRACE("Resetting curdepth at end of line to %d\n", lineCurDepth); + resetAtEndOfLine = FALSE; + curDepth = lineCurDepth; + } continue;
/* In a for loop, the DO command will follow a close bracket followed by @@ -2060,6 +2076,14 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE } else { prevDelim = CMD_PIPE; } + + /* If in an IF or ELSE statement, put subsequent chained + commands at a higher depth as if brackets were supplied + but remember to reset to the original depth at EOL */ + if ((inIf || inElse) && curDepth == lineCurDepth) { + curDepth++; + resetAtEndOfLine = TRUE; + } } else { curCopyTo[(*curLen)++] = *curPos; } @@ -2157,6 +2181,13 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE } else { prevDelim = CMD_NONE; } + /* If in an IF or ELSE statement, put subsequent chained + commands at a higher depth as if brackets were supplied + but remember to reset to the original depth at EOL */ + if ((inIf || inElse) && curDepth == lineCurDepth) { + curDepth++; + resetAtEndOfLine = TRUE; + } } else { curCopyTo[(*curLen)++] = *curPos; } @@ -2219,7 +2250,15 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE &curCopyTo, &curLen, prevDelim, curDepth, &lastEntry, output); - } + + /* If we had a single line if or else, and we pretended to add + brackets, end them now */ + if (resetAtEndOfLine) { + WINE_TRACE("Resetting curdepth at end of line to %d\n", lineCurDepth); + resetAtEndOfLine = FALSE; + curDepth = lineCurDepth; + } + }
/* If we have reached the end of the string, see if bracketing or final caret is outstanding */
Fixes bug#39906
for /f can run a program and parse its output. The program name can supply args and be quoted or not. If quoted, wine fails to run the program because internally we were adding an extra pair of quotes. These are not needed and can be removed.
Signed-off-by: Jason Edmeades us@edmeades.me.uk --- programs/cmd/builtins.c | 2 +- programs/cmd/tests/test_builtins.cmd | 4 +++- programs/cmd/tests/test_builtins.cmd.exp | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index f2a92c1a20..133ee53df2 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -2081,7 +2081,7 @@ static HANDLE WCMD_forf_getinputhandle(BOOL usebackq, WCHAR *itemstr, BOOL iscmd static const WCHAR redirOutW[] = {'>','%','s','\0'}; static const WCHAR cmdW[] = {'C','M','D','\0'}; static const WCHAR cmdslashcW[] = {'C','M','D','.','E','X','E',' ', - '/','C',' ','"','%','s','"','\0'}; + '/','C',' ','%','s','\0'};
/* Remove leading and trailing character */ if ((iscmd && (itemstr[0] == '`' && usebackq)) || diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd index d794805b71..b838485f45 100644 --- a/programs/cmd/tests/test_builtins.cmd +++ b/programs/cmd/tests/test_builtins.cmd @@ -1707,9 +1707,11 @@ if "%CD%"=="" goto :SkipFORFcmdNT4 for /f %%i in ('echo.Passed1') do echo %%i for /f "usebackq" %%i in (`echo.Passed2`) do echo %%i for /f usebackq %%i in (`echo.Passed3`) do echo %%i +for /f "usebackq" %%i in (`"c:\windows\system32\cmd.exe" /C echo Passed4`) do echo %%i +for /f "usebackq" %%i in (`""c:\windows\system32\cmd.exe" /C echo Passed5"`) do echo %%i goto :ContinueFORF :SkipFORFcmdNT4 -for /l %%i in (1,1,3) do echo Missing functionality - Broken%%i +for /l %%i in (1,1,5) do echo Missing functionality - Broken%%i :ContinueFORF rem FIXME: Rest not testable right now in wine: not implemented and would need rem preliminary grep-like program implementation (e.g. like findstr or fc) even diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 9e77e98c25..ffdd316cb6 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -1206,6 +1206,8 @@ c Passed1@or_broken@Missing functionality - Broken1 Passed2@or_broken@Missing functionality - Broken2 Passed3@or_broken@Missing functionality - Broken3 +Passed4@or_broken@Missing functionality - Broken4 +Passed5@or_broken@Missing functionality - Broken5 ------ eol option and@or_broken@Broken NT4 functionality1 Line@or_broken@Broken NT4 functionality2
Fixes regression reported under bug#45506
'if exists' takes a parameter which can be directory, directory\ or directory. for example, and should equate to true if the directory exists. The syntax directory\ is explicitly rejected by FindFirstFile and hence was not working - look for this specific case, and if found append a '.'.
Note I did consider just removing the trailing , but that could lead to false positives if there really was a file with the name being looked up as file. By appending a '.' it forces treatment of the parameter as a directory.
Signed-off-by: Jason Edmeades us@edmeades.me.uk --- programs/cmd/builtins.c | 9 ++++++++- programs/cmd/tests/test_builtins.cmd | 20 ++++++++++++++++++++ programs/cmd/tests/test_builtins.cmd.exp | 4 ++++ 3 files changed, 32 insertions(+), 1 deletion(-)
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index 133ee53df2..bb6367699c 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -2872,7 +2872,14 @@ void WCMD_if (WCHAR *p, CMD_LIST **cmdList) } else if (!lstrcmpiW (condition, existW)) { WIN32_FIND_DATAW fd; - HANDLE hff = FindFirstFileW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE), &fd); + HANDLE hff; + WCHAR *param = WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE); + int strlen = strlenW(param); + + /* FindFirstFile does not like a directory path ending in '', append a '.' */ + if (strlen && param[strlen-1] == '\') strcatW(param, dotW); + + hff = FindFirstFileW(param, &fd); test = (hff != INVALID_HANDLE_VALUE ); if (test) FindClose(hff);
diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd index b838485f45..d9338003c9 100644 --- a/programs/cmd/tests/test_builtins.cmd +++ b/programs/cmd/tests/test_builtins.cmd @@ -1029,6 +1029,26 @@ if exist idontexist\ba* ( ) else ( echo exist wildcard bad subdir broken works ) +if exist subdir ( + echo exist subdir ok +) else ( + echo ERROR exist subdir not working +) +if exist subdir. ( + echo exist subdir with . ok +) else ( + echo ERROR exist subdir with . not working +) +if exist subdir\ ( + echo exist subdir with \ ok +) else ( + echo ERROR exist subdir with \ not working +) +if exist "subdir" ( + echo exist subdir with \ and quotes ok +) else ( + echo ERROR exist subdir with \ and quotes not working +) del foo subdir\bar rd subdir
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index ffdd316cb6..539014ce21 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -780,6 +780,10 @@ exist simple wildcard works exist wildcard works negate exist wildcard works exist wildcard bad subdir broken works +exist subdir ok +exist subdir with . ok +exist subdir with \ ok +exist subdir with \ and quotes ok ------ for numbers negative numbers handled negative numbers handled
Fixes bug#44369
When a batch label is called, %0 and %~0 should be the label being called, and if you start adding modifiers to it (eg %~d0) then you get details of the batch program containing the label.
Signed-off-by: Jason Edmeades us@edmeades.me.uk --- programs/cmd/batch.c | 23 +++++++++++++---------- programs/cmd/tests/test_builtins.cmd | 9 +++++++++ programs/cmd/tests/test_builtins.cmd.exp | 3 +++ 3 files changed, 25 insertions(+), 10 deletions(-)
diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c index e10baf852c..75f377cf50 100644 --- a/programs/cmd/batch.c +++ b/programs/cmd/batch.c @@ -460,10 +460,19 @@ void WCMD_HandleTildaModifiers(WCHAR **start, BOOL atExecute) } if (lastModifier == firstModifier) return; /* Invalid syntax */
- /* Extract the parameter to play with */ - if (*lastModifier == '0') { + /* So now, firstModifier points to beginning of modifiers, lastModifier + points to the variable just after the modifiers. Process modifiers + in a specific order, remembering there could be duplicates */ + modifierLen = lastModifier - firstModifier; + finaloutput[0] = 0x00; + + /* Extract the parameter to play with + Special case param 0 - With %~0 you get the batch label which was called + whereas if you start applying other modifiers to it, you get the filename + the batch label is in */ + if (*lastModifier == '0' && modifierLen > 1) { strcpyW(outputparam, context->batchfileW); - } else if ((*lastModifier >= '1' && *lastModifier <= '9')) { + } else if ((*lastModifier >= '0' && *lastModifier <= '9')) { strcpyW(outputparam, WCMD_parameter (context -> command, *lastModifier-'0' + context -> shift_count[*lastModifier-'0'], @@ -473,12 +482,6 @@ void WCMD_HandleTildaModifiers(WCHAR **start, BOOL atExecute) strcpyW(outputparam, forloopcontext.variable[foridx]); }
- /* So now, firstModifier points to beginning of modifiers, lastModifier - points to the variable just after the modifiers. Process modifiers - in a specific order, remembering there could be duplicates */ - modifierLen = lastModifier - firstModifier; - finaloutput[0] = 0x00; - /* 1. Handle '~' : Strip surrounding quotes */ if (outputparam[0]=='"' && memchrW(firstModifier, '~', modifierLen) != NULL) { @@ -728,7 +731,7 @@ void WCMD_call (WCHAR *command) { li.QuadPart = 0; li.u.LowPart = SetFilePointer(context -> h, li.u.LowPart, &li.u.HighPart, FILE_CURRENT); - WCMD_batch (param1, command, TRUE, gotoLabel, context->h); + WCMD_batch (context->batchfileW, command, TRUE, gotoLabel, context->h); SetFilePointer(context -> h, li.u.LowPart, &li.u.HighPart, FILE_BEGIN);
diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd index d9338003c9..5cff817c4f 100644 --- a/programs/cmd/tests/test_builtins.cmd +++ b/programs/cmd/tests/test_builtins.cmd @@ -670,6 +670,15 @@ echo '%~xs1' goto :eof :endEchoFuns
+echo ------------ Testing parameter zero ------------ +call :func parm1 parm2 +goto :endParm0 +:func +echo %~0 %~1 +echo [%0] [%~d0] [%~p0] [%~n0] [%~x0] [%~s0] +goto :EOF +:endParm0 + echo ------------ Testing variable delayed expansion ------------ rem NT4 doesn't support this echo --- default mode (load-time expansion) diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 539014ce21..62e47b3b5e 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -575,6 +575,9 @@ N @drive@ '' '.eh'@or_broken@'' +------------ Testing parameter zero ------------ +:func parm1 +[:func] [@drive@] [@path@] [test] [.cmd] [@drive@@shortpath@test.cmd] ------------ Testing variable delayed expansion ------------ --- default mode (load-time expansion) foo