This MR mainly tackle the exit code generated from running cmd.exe with /c command line option. Things are a little bit torturous (as shown by tests) and require a bit of refactoring. The /k option is not covered as the tests trigger other bugs that we'll need to tackle beforehand (mainly proper escaping when rewriting builtin commands in pipe, that we'll need also for real pipes implementation, and also some echo features).
From: Eric Pouech epouech@codeweavers.com
Especially when running another cmd.exe instance, or using GOTO.
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/tests/test_builtins.cmd | 68 ++++++++++++++++++++++-- programs/cmd/tests/test_builtins.cmd.exp | 42 ++++++++++++++- 2 files changed, 105 insertions(+), 5 deletions(-)
diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd index 7db955f634d..d153c723549 100644 --- a/programs/cmd/tests/test_builtins.cmd +++ b/programs/cmd/tests/test_builtins.cmd @@ -855,6 +855,43 @@ echo --- success/failure for PAUSE command call :setError 666 & (pause < NUL > NUL 2>&1 &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) rem TODO: pause is harder to test when fd 1 is a console handle as we don't control output echo --- +rem end of duplication with builtin.bat (cf note above) +echo --------- success/failure when invoking cmd /c -------------- +echo @call :setError %%1>sel.bat +echo @goto :eof>>sel.bat +echo :setError>>sel.bat +echo @exit /b %%1>>sel.bat +echo @exit /b %%1>selng.bat +echo dir IDontExist.DoI> ec.cmd +call :setError 666 & ((cmd.exe /c "echo a") &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((cmd.exe /c "dir IDontExist.DoI">nul) &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((cmd.exe /c "ec.cmd">nul) &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((cmd.exe /c "exit /b 457") &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((cmd.exe /c "sel.bat 458") &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((cmd.exe /c "selng.bat 459") &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((cmd.exe /c "call sel.bat 460") &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((cmd.exe /c "call selng.bat 461") &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((cmd.exe /c "rmdir sel.bat") &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((cmd.exe /c "IDontExist.exe") &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +rem syntax error +call :setError 666 & ((cmd.exe /c "echo>") &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +echo --------- success/failure when invoking cmd /k -------------- +rem a bit convoluted, but we need to escape twice the errorlevel so that it's properly +rem evaluated inside the 'cmd /k' process +call :setError 666 & ((echo echo ERRORLEVEL ^^%%errorlevel^^%%| cmd.exe /q /k "@echo off & echo a") &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((echo echo ERRORLEVEL ^^%%errorlevel^^%%| cmd.exe /q /k "@echo off & (dir IDontExist.DoI >nul)") &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((echo echo ERRORLEVEL ^^%%errorlevel^^%%| cmd.exe /q /k "@echo off & (ec.cmd >nul)") &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((echo echo ERRORLEVEL ^^%%errorlevel^^%%| cmd.exe /q /k "@echo off & exit /b 457") &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((echo echo ERRORLEVEL ^^%%errorlevel^^%%| cmd.exe /q /k "@echo off & sel.bat 458") &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((echo echo ERRORLEVEL ^^%%errorlevel^^%%| cmd.exe /q /k "@echo off & selng.bat 459") &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((echo echo ERRORLEVEL ^^%%errorlevel^^%%| cmd.exe /q /k "@echo off & call sel.bat 460") &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((echo echo ERRORLEVEL ^^%%errorlevel^^%%| cmd.exe /q /k "@echo off & call selng.bat 461") &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((echo echo ERRORLEVEL ^^%%errorlevel^^%%| cmd.exe /q /k "@echo off & rmdir sel.bat") &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((echo echo ERRORLEVEL ^^%%errorlevel^^%%| cmd.exe /q /k "@echo off & IDontExist.exe") &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +rem syntax error +call :setError 666 & ((echo echo ERRORLEVEL ^^%%errorlevel^^%%| cmd.exe /q /k "@echo off & echo>") &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +erase /q sel.bat ec.cmd + setlocal DisableDelayedExpansion echo ------------ Testing 'set' ------------ call :setError 0 @@ -3944,6 +3981,7 @@ if not errorlevel 1 echo errorlevel zero, good if not errorlevel 0x1 echo hexa should not be recognized! if not errorlevel 1a echo invalid error level recognized! rem Now verify that setting a real variable hides its magic variable +setlocal set errorlevel=7 echo %ErrorLevel% should be 7 if errorlevel 7 echo setting var worked too well, bad @@ -3952,6 +3990,7 @@ echo %ErrorLevel% should still be 7 rem Verify that (call ) sets errorlevel to 0 (call ) if errorlevel 1 echo errorlevel should have been 0 +endlocal
echo ------------ Testing GOTO ------------ if a==a goto dest1 @@ -3992,22 +4031,26 @@ if exist cmd_output echo FAILURE at dest 6 as file exists echo Ignoring double colons worked del cmd_output >nul 2>&1
+del testgoto.bat >nul 2>&1 rem goto a label which does not exist issues an error message and rem acts the same as goto :EOF, and ensure ::label is never matched -del testgoto.bat >nul 2>&1 -echo goto :dest7 ^>nul 2^>^&1 >> testgoto.bat + +echo goto :dest7 foo ^>nul >> testgoto.bat echo echo FAILURE at dest 7 - Should have not found label and issued an error plus ended the batch>> testgoto.bat echo ::dest7>> testgoto.bat echo echo FAILURE at dest 7 - Incorrectly went to label >> testgoto.bat +call :setError 666 call testgoto.bat +echo dest7 %ERRORLEVEL% del testgoto.bat >nul 2>&1
-del testgoto.bat >nul 2>&1 -echo goto ::dest8 ^>nul 2^>^&1 >> testgoto.bat +echo goto ::dest8 foo ^>nul >> testgoto.bat echo echo FAILURE at dest 8 - Should have not found label and issued an error plus ended the batch>> testgoto.bat echo ::dest8>> testgoto.bat echo echo FAILURE at dest 8 - Incorrectly went to label >> testgoto.bat +call :setError 666 call testgoto.bat +echo dest8 %ERRORLEVEL% del testgoto.bat >nul 2>&1
if g==g goto dest9 @@ -4022,6 +4065,23 @@ echo FAILURE at dest 10 :dest10:this is also ignored echo Correctly ignored trailing information
+echo goto :eof foo bar >> testgoto.bat +echo echo FAILURE at dest eof - Should have not found label and issued an error plus ended the batch>> testgoto.bat +call :setError 666 +call testgoto.bat +echo desteof1 %ERRORLEVEL% +del testgoto.bat >nul 2>&1 + +echo goto :eof foo bar>> testgoto.bat +echo echo FAILURE at dest eof - Should have not found label and issued an error plus ended the batch>> testgoto.bat +echo :eof>> testgoto.bat +echo echo FAILURE at dest eof - Incorrectly went to label>> testgoto.bat +call :setError 666 +call testgoto.bat +echo desteof2 %ERRORLEVEL% +del testgoto.bat >nul 2>&1 + +echo --- rem Testing which label is reached when there are many options echo Begin: set nextlabel= diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 1d31b19e01c..d6a4acb3cc8 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -692,7 +692,42 @@ SUCCESS 0 --- success/failure for PAUSE command FAILURE 1 --- ------------- Testing 'set' ------------ +--------- success/failure when invoking cmd /c -------------- +a +SUCCESS 0 +FAILURE 1 +FAILURE 1 +FAILURE 457 +@todo_wine@SUCCESS 0 +FAILURE 459 +FAILURE 460 +FAILURE 461 +@todo_wine@FAILURE 267 +@todo_wine@FAILURE 1 +@todo_wine@FAILURE 1 +--------- success/failure when invoking cmd /k -------------- +a +@todo_wine@ERRORLEVEL 0 +SUCCESS 0 +@todo_wine@ERRORLEVEL 1 +SUCCESS 0 +@todo_wine@ERRORLEVEL 1 +SUCCESS 0 +FAILURE 457 +@todo_wine@ERRORLEVEL 458 +SUCCESS 0 +@todo_wine@ERRORLEVEL 459 +SUCCESS 0 +@todo_wine@ERRORLEVEL 460 +SUCCESS 0 +@todo_wine@ERRORLEVEL 461 +SUCCESS 0 +@todo_wine@ERRORLEVEL 0 +SUCCESS 0 +@todo_wine@ERRORLEVEL 9009 +SUCCESS 0 +@todo_wine@FAILURE 1 +@todo_wine@------------ Testing 'set' ------------ 1 0 WINE_FOOBAR not defined @@ -2122,8 +2157,13 @@ goto with a following space worked goto with following amphersands worked goto with redirections worked Ignoring double colons worked +dest7 1 +dest8 1 label with mixed whitespace and no echo worked Correctly ignored trailing information +desteof1 666 +desteof2 666 +--- Begin: ..First sub ..First sub
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/tests/test_builtins.cmd.exp | 6 +++--- programs/cmd/wcmdmain.c | 9 ++++----- 2 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index d6a4acb3cc8..35ef48a70fe 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -702,9 +702,9 @@ FAILURE 457 FAILURE 459 FAILURE 460 FAILURE 461 -@todo_wine@FAILURE 267 -@todo_wine@FAILURE 1 -@todo_wine@FAILURE 1 +FAILURE 267 +FAILURE 1 +FAILURE 1 --------- success/failure when invoking cmd /k -------------- a @todo_wine@ERRORLEVEL 0 diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index fbec86a481a..5b029b6a1ed 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -4491,23 +4491,22 @@ int __cdecl wmain (int argc, WCHAR *argvW[]) }
if (opt_c) { + RETURN_CODE return_code = NO_ERROR; /* If we do a "cmd /c command", we don't want to allocate a new * console since the command returns immediately. Rather, we use * the currently allocated input and output handles. This allows * us to pipe to and read from the command interpreter. */ - /* Parse the command string, without reading any more input */ rpl_status = WCMD_ReadAndParseLine(cmd, &toExecute); if (rpl_status == RPL_SUCCESS && toExecute) { - node_execute(toExecute); + return_code = node_execute(toExecute); node_dispose_tree(toExecute); } else if (rpl_status == RPL_SYNTAXERROR) - errorlevel = RETURN_CODE_SYNTAX_ERROR; - - return errorlevel; + return_code = ERROR_INVALID_FUNCTION; + return return_code; }
GetStartupInfoW(&startupInfo);
From: Eric Pouech epouech@codeweavers.com
Replace skip_rest field in batch context structure usng file position after EOF instead.
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/batch.c | 14 ++++++-------- programs/cmd/builtins.c | 6 +++--- programs/cmd/wcmd.h | 3 ++- programs/cmd/wcmdmain.c | 19 +++++++++---------- 4 files changed, 20 insertions(+), 22 deletions(-)
diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c index c5a0763c81f..16860993540 100644 --- a/programs/cmd/batch.c +++ b/programs/cmd/batch.c @@ -27,16 +27,15 @@ WINE_DEFAULT_DEBUG_CHANNEL(cmd); static RETURN_CODE WCMD_batch_main_loop(void) { RETURN_CODE return_code = NO_ERROR; + enum read_parse_line rpl; + CMD_NODE *node; + /* Work through the file line by line until an exit is called. */ - while (!context->skip_rest) + while ((rpl = WCMD_ReadAndParseLine(NULL, &node)) != RPL_EOF) { - CMD_NODE *node; - - switch (WCMD_ReadAndParseLine(NULL, &node)) + switch (rpl) { - case RPL_EOF: - context->skip_rest = TRUE; - break; + case RPL_EOF: break; /* never reached; get rid of warning */ case RPL_SUCCESS: if (node) { @@ -95,7 +94,6 @@ static struct batch_context *push_batch_context(WCHAR *command, struct batch_fil context->command = command; memset(context->shift_count, 0x00, sizeof(context->shift_count)); context->prev_context = prev; - context->skip_rest = FALSE; context->batch_file = batch_file; batch_file->ref_count++;
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index 21f15c32c4a..1532b94746c 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -1749,7 +1749,7 @@ RETURN_CODE WCMD_goto(void) /* Handle special :EOF label */ if (lstrcmpiW(L":eof", param1) == 0) { - context->skip_rest = TRUE; + context->file_position.QuadPart = WCMD_FILE_POSITION_EOF; return RETURN_CODE_ABORTED; } h = CreateFileW(context->batch_file->path_name, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, @@ -1770,7 +1770,7 @@ RETURN_CODE WCMD_goto(void) CloseHandle(h); if (ret) return RETURN_CODE_ABORTED; WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET)); - context->skip_rest = TRUE; + context->file_position.QuadPart = WCMD_FILE_POSITION_EOF; } return ERROR_INVALID_FUNCTION; } @@ -3754,7 +3754,7 @@ RETURN_CODE WCMD_exit(void) if (context && lstrcmpiW(quals, L"/B") == 0) { errorlevel = rc; - context -> skip_rest = TRUE; + context->file_position.QuadPart = WCMD_FILE_POSITION_EOF; return RETURN_CODE_ABORTED; } ExitProcess(rc); diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index f8ea742ee1c..dfc50147536 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -279,10 +279,11 @@ struct batch_context LARGE_INTEGER file_position; int shift_count[10]; /* Offset in terms of shifts for %0 - %9 */ struct batch_context *prev_context; /* Pointer to the previous context block */ - BOOL skip_rest; /* Skip the rest of the batch program and exit */ struct batch_file *batch_file; /* Reference to the file itself */ };
+#define WCMD_FILE_POSITION_EOF (~(DWORD64)0) + /* Data structure to handle building lists during recursive calls */
struct env_stack diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 5b029b6a1ed..e0f2dd32555 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -1779,15 +1779,17 @@ static RETURN_CODE run_full_path(const WCHAR *file, WCHAR *full_cmdline, BOOL ca if (ext && (!wcsicmp(ext, L".bat") || !wcsicmp(ext, L".cmd"))) { RETURN_CODE return_code; - BOOL oldinteractive = interactive; + BOOL prev_interactive = interactive; + BOOL prev_echo_mode = echo_mode;
interactive = FALSE; return_code = WCMD_call_batch(file, full_cmdline); - interactive = oldinteractive; + if ((interactive = prev_interactive)) + echo_mode = prev_echo_mode; if (context && !called) { TRACE("Batch completed, but was not 'called' so skipping outer batch too\n"); - context->skip_rest = TRUE; + context->file_position.QuadPart = WCMD_FILE_POSITION_EOF; } return return_code; } @@ -2351,10 +2353,8 @@ static RETURN_CODE execute_single_command(const WCHAR *command) return_code = WCMD_run_builtin_command(sc.cmd_index, cmd); else { - BOOL prev_echo_mode = echo_mode; if (*sc.path) return_code = run_full_path(sc.path, cmd, FALSE); - echo_mode = prev_echo_mode; } free(cmd); return return_code; @@ -2368,11 +2368,8 @@ RETURN_CODE WCMD_call_command(WCHAR *command) return_code = search_command(command, &sc, FALSE); if (return_code == NO_ERROR) { - unsigned old_echo_mode = echo_mode; if (!*sc.path) return NO_ERROR; - return_code = run_full_path(sc.path, command, TRUE); - if (interactive) echo_mode = old_echo_mode; - return return_code; + return run_full_path(sc.path, command, TRUE); }
if (sc.cmd_index <= WCMD_EXIT) @@ -4581,16 +4578,18 @@ int __cdecl wmain (int argc, WCHAR *argvW[])
if (opt_k) { + RETURN_CODE return_code = NO_ERROR; rpl_status = WCMD_ReadAndParseLine(cmd, &toExecute); /* Parse the command string, without reading any more input */ if (rpl_status == RPL_SUCCESS && toExecute) { - node_execute(toExecute); + return_code = node_execute(toExecute); node_dispose_tree(toExecute); } else if (rpl_status == RPL_SYNTAXERROR) errorlevel = RETURN_CODE_SYNTAX_ERROR; free(cmd); + if (return_code == RETURN_CODE_ABORTED) return errorlevel; }
/*
From: Eric Pouech epouech@codeweavers.com
This allows to: - simplify some internal code - get rid of global 'interactive' variable
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/batch.c | 24 +++---- programs/cmd/builtins.c | 12 ++-- programs/cmd/wcmd.h | 6 +- programs/cmd/wcmdmain.c | 149 +++++++++++++++++----------------------- 4 files changed, 82 insertions(+), 109 deletions(-)
diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c index 16860993540..42cb4c47b99 100644 --- a/programs/cmd/batch.c +++ b/programs/cmd/batch.c @@ -31,7 +31,7 @@ static RETURN_CODE WCMD_batch_main_loop(void) CMD_NODE *node;
/* Work through the file line by line until an exit is called. */ - while ((rpl = WCMD_ReadAndParseLine(NULL, &node)) != RPL_EOF) + while ((rpl = WCMD_ReadAndParseLine(&node)) != RPL_EOF) { switch (rpl) { @@ -50,7 +50,8 @@ static RETURN_CODE WCMD_batch_main_loop(void) }
/* If there are outstanding setlocal's to the current context, unwind them. */ - while (WCMD_endlocal() == NO_ERROR) {} + if (WCMD_is_in_context(NULL)) + while (WCMD_endlocal() == NO_ERROR) {}
return return_code; } @@ -62,6 +63,7 @@ static struct batch_file *find_or_alloc_batch_file(const WCHAR *file) HANDLE h; unsigned int i;
+ if (!file) return NULL; for (ctx = context; ctx; ctx = ctx->prev_context) { if (ctx->batch_file && !wcscmp(ctx->batch_file->path_name, file)) @@ -95,7 +97,7 @@ static struct batch_context *push_batch_context(WCHAR *command, struct batch_fil memset(context->shift_count, 0x00, sizeof(context->shift_count)); context->prev_context = prev; context->batch_file = batch_file; - batch_file->ref_count++; + if (batch_file) batch_file->ref_count++;
return context; } @@ -119,32 +121,26 @@ static struct batch_context *pop_batch_context(struct batch_context *ctx) }
/**************************************************************************** - * WCMD_batch + * WCMD_call_batch * * Open and execute a batch file. * On entry *command includes the complete command line beginning with the name * of the batch file (if a CALL command was entered the CALL has been removed). * *file is the name of the file, which might not exist and may not have the - * .BAT suffix on. Called is 1 for a CALL, 0 otherwise. + * .BAT suffix on. * * We need to handle recursion correctly, since one batch program might call another. * So parameters for this batch file are held in a BATCH_CONTEXT structure. - * - * To support call within the same batch program, another input parameter is - * a label to goto once opened. */ - RETURN_CODE WCMD_call_batch(const WCHAR *file, WCHAR *command) { - RETURN_CODE return_code = NO_ERROR; + RETURN_CODE return_code;
context = push_batch_context(command, find_or_alloc_batch_file(file), 0); return_code = WCMD_batch_main_loop(); context = pop_batch_context(context);
- if (return_code != NO_ERROR && return_code != RETURN_CODE_ABORTED) - errorlevel = return_code; - return errorlevel; + return return_code; }
/******************************************************************* @@ -424,7 +420,7 @@ void WCMD_HandleTildeModifiers(WCHAR **start, BOOL atExecute) 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) { + if (*lastModifier == '0' && modifierLen > 1 && context->batch_file) { lstrcpyW(outputparam, context->batch_file->path_name); } else if ((*lastModifier >= '0' && *lastModifier <= '9')) { lstrcpyW(outputparam, diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index 1532b94746c..589f31ea930 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -36,7 +36,6 @@ WINE_DEFAULT_DEBUG_CHANNEL(cmd);
extern int defaultColor; extern BOOL echo_mode; -extern BOOL interactive;
struct env_stack *pushd_directories; const WCHAR inbuilt[][10] = { @@ -841,7 +840,7 @@ RETURN_CODE WCMD_copy(WCHAR * args) else { /* By default, we will force the overwrite in batch mode and ask for * confirmation in interactive mode. */ - prompt = interactive; + prompt = !context; /* If COPYCMD is set, then we force the overwrite with /Y and ask for * confirmation with /-Y. If COPYCMD is neither of those, then we use the * default behavior. */ @@ -1746,6 +1745,7 @@ RETURN_CODE WCMD_goto(void) return ERROR_INVALID_FUNCTION; }
+ if (!context->batch_file) return ERROR_INVALID_FUNCTION; /* Handle special :EOF label */ if (lstrcmpiW(L":eof", param1) == 0) { @@ -1932,7 +1932,7 @@ RETURN_CODE WCMD_move(void) else { /* By default, we will force the overwrite in batch mode and ask for * confirmation in interactive mode. */ - force = !interactive; + force = !!context; /* If COPYCMD is set, then we force the overwrite with /Y and ask for * confirmation with /-Y. If COPYCMD is neither of those, then we use the * default behavior. */ @@ -2195,7 +2195,7 @@ RETURN_CODE WCMD_setlocal(WCHAR *args) WCHAR *argN = args;
/* setlocal does nothing outside of batch programs */ - if (!context) + if (!WCMD_is_in_context(NULL)) return NO_ERROR; newdelay = delayedsubst; while (argN) @@ -2251,7 +2251,7 @@ RETURN_CODE WCMD_endlocal(void) int len, n;
/* setlocal does nothing outside of batch programs */ - if (!context) return NO_ERROR; + if (!WCMD_is_in_context(NULL)) return NO_ERROR;
/* setlocal needs a saved environment from within the same context (batch program) as it was saved in */ @@ -3126,7 +3126,7 @@ RETURN_CODE WCMD_setshow_env(WCHAR *s) return_code = ERROR_INVALID_FUNCTION; } /* If we have no context (interactive or cmd.exe /c) print the final result */ - else if (!context) { + else if (!WCMD_is_in_context(NULL)) { swprintf(string, ARRAY_SIZE(string), L"%d", result); WCMD_output_asis(string); } diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index dfc50147536..7a1c2e50425 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -215,7 +215,7 @@ BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars, LPDWO BOOL WCMD_read_console(const HANDLE hInput, WCHAR *inputBuffer, const DWORD inputBufferLength, LPDWORD numRead);
enum read_parse_line {RPL_SUCCESS, RPL_EOF, RPL_SYNTAXERROR}; -enum read_parse_line WCMD_ReadAndParseLine(const WCHAR *initialcmd, CMD_NODE **output); +enum read_parse_line WCMD_ReadAndParseLine(CMD_NODE **output); void node_dispose_tree(CMD_NODE *cmds); RETURN_CODE node_execute(CMD_NODE *node);
@@ -344,14 +344,14 @@ extern BOOL delayedsubst; static inline BOOL WCMD_is_in_context(const WCHAR *ext) { size_t c_len, e_len; - if (!context) return FALSE; + if (!context || !context->batch_file) return FALSE; if (!ext) return TRUE; c_len = wcslen(context->batch_file->path_name); e_len = wcslen(ext); return (c_len > e_len) && !wcsicmp(&context->batch_file->path_name[c_len - e_len], ext); }
- #endif /* !RC_INVOKED */ +#endif /* !RC_INVOKED */
/* * Serial nos of builtin commands. These constants must be in step with diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index e0f2dd32555..c16662ce70a 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -56,7 +56,6 @@ extern struct env_stack *pushd_directories; struct batch_context *context = NULL; int errorlevel; WCHAR quals[MAXSTRING], param1[MAXSTRING], param2[MAXSTRING]; -BOOL interactive; FOR_CONTEXT *forloopcontext; /* The 'for' loop context */ BOOL delayedsubst = FALSE; /* The current delayed substitution setting */
@@ -1010,7 +1009,7 @@ static WCHAR *WCMD_expand_envvar(WCHAR *start) endOfVar = wcschr(start + 1, *start); if (!endOfVar) /* no corresponding closing char... either skip startchar in batch, or leave untouched otherwise */ - return context ? WCMD_strsubstW(start, start + 1, NULL, 0) : start + 1; + return WCMD_is_in_context(NULL) ? WCMD_strsubstW(start, start + 1, NULL, 0) : start + 1;
memcpy(thisVar, start + 1, (endOfVar - start - 1) * sizeof(WCHAR)); thisVar[endOfVar - start - 1] = L'\0'; @@ -1051,7 +1050,7 @@ static WCHAR *WCMD_expand_envvar(WCHAR *start) if (!len) { /* Command line - just ignore this */ - if (context == NULL) return endOfVar + 1; + if (!WCMD_is_in_context(NULL)) return endOfVar + 1;
/* Batch - replace unknown env var with nothing */ if (colonpos == NULL) @@ -1207,23 +1206,23 @@ static void handleExpansion(WCHAR *cmd, BOOL atExecute) {
/* handle consecutive % or ! */ if ((!atExecute || startchar == L'!') && p[1] == startchar) { - if (context) WCMD_strsubstW(p, p + 1, NULL, 0); - if (!context || startchar == L'%') p++; + if (WCMD_is_in_context(NULL)) WCMD_strsubstW(p, p + 1, NULL, 0); + if (!WCMD_is_in_context(NULL) || startchar == L'%') p++; /* Replace %~ modifications if in batch program */ } else if (p[1] == L'~' && p[2] && !iswspace(p[2])) { WCMD_HandleTildeModifiers(&p, atExecute); p++;
/* Replace use of %0...%9 if in batch program*/ - } else if (!atExecute && context && (i >= 0) && (i <= 9) && startchar == '%') { - t = WCMD_parameter(context -> command, i + context -> shift_count[i], + } else if (!atExecute && WCMD_is_in_context(NULL) && (i >= 0) && (i <= 9) && startchar == L'%') { + t = WCMD_parameter(context->command, i + context->shift_count[i], NULL, TRUE, TRUE); p = WCMD_strsubstW(p, p+2, t, -1);
/* Replace use of %* if in batch program*/ - } else if (!atExecute && context && *(p+1)=='*' && startchar == '%') { + } else if (!atExecute && WCMD_is_in_context(NULL) && p[1] == L'*' && startchar == L'%') { WCHAR *startOfParms = NULL; - WCHAR *thisParm = WCMD_parameter(context -> command, 0, &startOfParms, TRUE, TRUE); + WCHAR *thisParm = WCMD_parameter(context->command, 0, &startOfParms, TRUE, TRUE); if (startOfParms != NULL) { startOfParms += lstrlenW(thisParm); while (*startOfParms==' ' || *startOfParms == '\t') startOfParms++; @@ -1239,7 +1238,7 @@ static void handleExpansion(WCHAR *cmd, BOOL atExecute) { BOOL first = p == cmd; p = WCMD_expand_envvar(p); /* FIXME: maybe this more likely calls for a specific handling of first arg? */ - if (context && startchar == L'!' && first) + if (WCMD_is_in_context(NULL) && startchar == L'!' && first) { WCHAR *last; for (last = p; *last == startchar; last++) {} @@ -1779,12 +1778,15 @@ static RETURN_CODE run_full_path(const WCHAR *file, WCHAR *full_cmdline, BOOL ca if (ext && (!wcsicmp(ext, L".bat") || !wcsicmp(ext, L".cmd"))) { RETURN_CODE return_code; - BOOL prev_interactive = interactive; BOOL prev_echo_mode = echo_mode;
- interactive = FALSE; return_code = WCMD_call_batch(file, full_cmdline); - if ((interactive = prev_interactive)) + if (return_code == RETURN_CODE_ABORTED) + return_code = errorlevel; + else if (return_code != NO_ERROR) + errorlevel = return_code; + + if (!context) echo_mode = prev_echo_mode; if (context && !called) { @@ -1837,7 +1839,7 @@ static RETURN_CODE run_full_path(const WCHAR *file, WCHAR *full_cmdline, BOOL ca } }
- if (!interactive || (console && !HIWORD(console))) + if (context || (console && !HIWORD(console))) WaitForSingleObject(handle, INFINITE); GetExitCodeProcess(handle, &exit_code); errorlevel = (exit_code == STILL_ACTIVE) ? NO_ERROR : exit_code; @@ -3092,48 +3094,53 @@ static void lexer_push_command(struct node_builder *builder, *copyTo = command; }
-static WCHAR *fetch_next_line(BOOL feed, BOOL first_line, WCHAR* buffer) +static WCHAR *fetch_next_line(BOOL first_line, WCHAR* buffer) { - /* display prompt */ - if (interactive && !context) + BOOL ret; + + if (!context) /* interactive mode */ { /* native does is this way... not symmetrical wrt. echo_mode */ if (!first_line) WCMD_output_asis(WCMD_LoadMessage(WCMD_MOREPROMPT)); else if (echo_mode) WCMD_show_prompt(); + ret = !!WCMD_fgets(buffer, MAXSTRING, GetStdHandle(STD_INPUT_HANDLE)); } - - if (feed) + else if (context->batch_file) /* command file */ { - BOOL ret; - if (context) + LARGE_INTEGER zeroli = {.QuadPart = 0}; + HANDLE h = CreateFileW(context->batch_file->path_name, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (h == INVALID_HANDLE_VALUE) { - LARGE_INTEGER zeroli = {.QuadPart = 0}; - HANDLE h = CreateFileW(context->batch_file->path_name, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, - NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (h == INVALID_HANDLE_VALUE) - { - SetLastError(ERROR_FILE_NOT_FOUND); - WCMD_print_error(); - ret = FALSE; - } - else - { - ret = SetFilePointerEx(h, context->file_position, NULL, FILE_BEGIN) && - !!WCMD_fgets(buffer, MAXSTRING, h) && - SetFilePointerEx(h, zeroli, &context->file_position, FILE_CURRENT); - CloseHandle(h); - } + SetLastError(ERROR_FILE_NOT_FOUND); + WCMD_print_error(); + ret = FALSE; } else - ret = !!WCMD_fgets(buffer, MAXSTRING, GetStdHandle(STD_INPUT_HANDLE)); - if (!ret) { - buffer[0] = L'\0'; - return NULL; + ret = SetFilePointerEx(h, context->file_position, NULL, FILE_BEGIN) && + !!WCMD_fgets(buffer, MAXSTRING, h) && + SetFilePointerEx(h, zeroli, &context->file_position, FILE_CURRENT); + CloseHandle(h); + } + } + else /* /c or /k string from command line */ + { + if ((ret = (context->file_position.QuadPart == 0))) + { + wcscpy(buffer, context->command); + context->file_position.QuadPart += wcslen(context->command) + 1; } } + + if (!ret) + { + buffer[0] = L'\0'; + return NULL; + } + /* Handle truncated input - issue warning */ if (wcslen(buffer) == MAXSTRING - 1) { @@ -3146,7 +3153,7 @@ static WCHAR *fetch_next_line(BOOL feed, BOOL first_line, WCHAR* buffer)
buffer = WCMD_skip_leading_spaces(buffer); /* Show prompt before batch line IF echo is on and in batch program */ - if (context && echo_mode && *buffer && *buffer != '@') + if (WCMD_is_in_context(NULL) && echo_mode && *buffer && *buffer != '@') { if (first_line) { @@ -3228,7 +3235,7 @@ static BOOL lexer_white_space_only(const WCHAR *string, int len) * - Anything else gets put into the command string (including * redirects) */ -enum read_parse_line WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_NODE **output) +enum read_parse_line WCMD_ReadAndParseLine(CMD_NODE **output) { WCHAR *curPos; WCHAR curString[MAXSTRING]; @@ -3246,10 +3253,7 @@ enum read_parse_line WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_NODE ** if (!extraSpace) extraSpace = xalloc((MAXSTRING + 1) * sizeof(WCHAR));
- /* If initial command read in, use that, otherwise get input from handle */ - if (optionalcmd) - wcscpy(extraSpace, optionalcmd); - if (!(curPos = fetch_next_line(optionalcmd == NULL, TRUE, extraSpace))) + if (!(curPos = fetch_next_line(TRUE, extraSpace))) return RPL_EOF;
TRACE("About to parse line (%ls)\n", extraSpace); @@ -3285,8 +3289,7 @@ enum read_parse_line WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_NODE ** node_builder_push_token(&builder, TKN_EOL);
/* If we have reached the end of the string, see if bracketing is outstanding */ - if (builder.opened_parenthesis > 0 && optionalcmd == NULL && - (curPos = fetch_next_line(TRUE, FALSE, extraSpace))) + if (builder.opened_parenthesis > 0 && (curPos = fetch_next_line(FALSE, extraSpace))) { TRACE("Need to read more data as outstanding brackets or carets\n"); } @@ -3470,13 +3473,12 @@ enum read_parse_line WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_NODE ** if (curPos[1] == L'\0') { TRACE("Caret found at end of line\n"); extraSpace[0] = L'^'; - if (optionalcmd) break; - if (!fetch_next_line(TRUE, FALSE, extraSpace + 1)) + if (!fetch_next_line(FALSE, extraSpace + 1)) break; if (!extraSpace[1]) /* empty line */ { extraSpace[1] = L'\r'; - if (!fetch_next_line(TRUE, FALSE, extraSpace + 2)) + if (!fetch_next_line(FALSE, extraSpace + 2)) break; } curPos = extraSpace; @@ -4328,9 +4330,6 @@ int __cdecl wmain (int argc, WCHAR *argvW[]) WCMD_echo(L"OFF"); }
- /* Until we start to read from the keyboard, stay as non-interactive */ - interactive = FALSE; - SetEnvironmentVariableW(L"PROMPT", L"$P$G");
if (opt_c || opt_k) { @@ -4487,25 +4486,13 @@ int __cdecl wmain (int argc, WCHAR *argvW[]) WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string)); }
- if (opt_c) { - RETURN_CODE return_code = NO_ERROR; - /* If we do a "cmd /c command", we don't want to allocate a new - * console since the command returns immediately. Rather, we use - * the currently allocated input and output handles. This allows - * us to pipe to and read from the command interpreter. - */ - /* Parse the command string, without reading any more input */ - rpl_status = WCMD_ReadAndParseLine(cmd, &toExecute); - if (rpl_status == RPL_SUCCESS && toExecute) - { - return_code = node_execute(toExecute); - node_dispose_tree(toExecute); - } - else if (rpl_status == RPL_SYNTAXERROR) - return_code = ERROR_INVALID_FUNCTION; - return return_code; + if (opt_c) + { + RETURN_CODE return_code = WCMD_call_batch(NULL, cmd); + if (return_code != RETURN_CODE_ABORTED && return_code != NO_ERROR) + return return_code; + return errorlevel; } - GetStartupInfoW(&startupInfo); if (startupInfo.lpTitle != NULL) SetConsoleTitleW(startupInfo.lpTitle); @@ -4578,29 +4565,19 @@ int __cdecl wmain (int argc, WCHAR *argvW[])
if (opt_k) { - RETURN_CODE return_code = NO_ERROR; - rpl_status = WCMD_ReadAndParseLine(cmd, &toExecute); - /* Parse the command string, without reading any more input */ - if (rpl_status == RPL_SUCCESS && toExecute) - { - return_code = node_execute(toExecute); - node_dispose_tree(toExecute); - } - else if (rpl_status == RPL_SYNTAXERROR) - errorlevel = RETURN_CODE_SYNTAX_ERROR; - free(cmd); + RETURN_CODE return_code = WCMD_call_batch(NULL, cmd); if (return_code == RETURN_CODE_ABORTED) return errorlevel; + free(cmd); }
/* * Loop forever getting commands and executing them. */
- interactive = TRUE; if (!opt_k) WCMD_output_asis(version_string); if (echo_mode) WCMD_output_asis(L"\r\n"); /* Read until EOF (which for std input is never, but if redirect in place, may occur */ - while ((rpl_status = WCMD_ReadAndParseLine(NULL, &toExecute)) != RPL_EOF) + while ((rpl_status = WCMD_ReadAndParseLine(&toExecute)) != RPL_EOF) { if (rpl_status == RPL_SUCCESS && toExecute) {
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/wcmdmain.c | 69 +++++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 26 deletions(-)
diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index c16662ce70a..d36a36d67aa 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -1762,9 +1762,8 @@ static void init_msvcrt_io_block(STARTUPINFOW* st) }
/* Attempt to open a file at a known path. */ -static RETURN_CODE run_full_path(const WCHAR *file, WCHAR *full_cmdline, BOOL called) +static RETURN_CODE run_external_full_path(const WCHAR *file, WCHAR *full_cmdline) { - const WCHAR *ext = wcsrchr(file, '.'); STARTUPINFOW si = {.cb = sizeof(si)}; DWORD console, exit_code; WCHAR exe_path[MAX_PATH]; @@ -1775,27 +1774,6 @@ static RETURN_CODE run_full_path(const WCHAR *file, WCHAR *full_cmdline, BOOL ca
TRACE("%s\n", debugstr_w(file));
- if (ext && (!wcsicmp(ext, L".bat") || !wcsicmp(ext, L".cmd"))) - { - RETURN_CODE return_code; - BOOL prev_echo_mode = echo_mode; - - return_code = WCMD_call_batch(file, full_cmdline); - if (return_code == RETURN_CODE_ABORTED) - return_code = errorlevel; - else if (return_code != NO_ERROR) - errorlevel = return_code; - - if (!context) - echo_mode = prev_echo_mode; - if (context && !called) - { - TRACE("Batch completed, but was not 'called' so skipping outer batch too\n"); - context->file_position.QuadPart = WCMD_FILE_POSITION_EOF; - } - return return_code; - } - if ((INT_PTR)FindExecutableW(file, NULL, exe_path) < 32) console = 0; else @@ -1848,11 +1826,28 @@ static RETURN_CODE run_full_path(const WCHAR *file, WCHAR *full_cmdline, BOOL ca return errorlevel; }
+static RETURN_CODE run_command_file(const WCHAR *file, WCHAR *full_cmdline) +{ + RETURN_CODE return_code; + BOOL prev_echo_mode = echo_mode; + + return_code = WCMD_call_batch(file, full_cmdline); + if (return_code == RETURN_CODE_ABORTED) + return_code = errorlevel; + else if (return_code != NO_ERROR) + errorlevel = return_code; + + if (!context) + echo_mode = prev_echo_mode; + return return_code; +} + struct search_command { WCHAR path[MAX_PATH]; BOOL has_path; /* if input has path part (ie cannot be a builtin command) */ BOOL has_extension; /* if extension was given to input */ + BOOL is_command_file; /* when has_path is set, tells whether its a command file, or an external executable */ int cmd_index; /* potential index to builtin command */ };
@@ -2087,7 +2082,12 @@ static RETURN_CODE search_command(WCHAR *command, struct search_command *sc, BOO } /* if foo.bat was given but not found, try to match foo.bat.bat (or any valid ext) */ if (!found) found = search_in_pathext(sc->path); - if (found) return NO_ERROR; + if (found) + { + const WCHAR *ext = wcsrchr(sc->path, '.'); + sc->is_command_file = ext && (!wcsicmp(ext, L".bat") || !wcsicmp(ext, L".cmd")); + return NO_ERROR; + } } return RETURN_CODE_CANT_LAUNCH; } @@ -2356,7 +2356,20 @@ static RETURN_CODE execute_single_command(const WCHAR *command) else { if (*sc.path) - return_code = run_full_path(sc.path, cmd, FALSE); + { + if (sc.is_command_file) + { + return_code = run_command_file(sc.path, cmd); + if (context) + { + TRACE("Batch completed, but was not 'called' so skipping outer batch too\n"); + context->file_position.QuadPart = WCMD_FILE_POSITION_EOF; + } + } + else + return_code = run_external_full_path(sc.path, cmd); + } + } free(cmd); return return_code; @@ -2371,7 +2384,11 @@ RETURN_CODE WCMD_call_command(WCHAR *command) if (return_code == NO_ERROR) { if (!*sc.path) return NO_ERROR; - return run_full_path(sc.path, command, TRUE); + if (sc.is_command_file) + return_code = run_command_file(sc.path, command); + else + return_code = run_external_full_path(sc.path, command); + return return_code; }
if (sc.cmd_index <= WCMD_EXIT)
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/builtins.c | 4 +- programs/cmd/tests/test_builtins.cmd.exp | 2 +- programs/cmd/wcmd.h | 9 ++++- programs/cmd/wcmdmain.c | 48 +++++++++++++++--------- 4 files changed, 42 insertions(+), 21 deletions(-)
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index 589f31ea930..78ef855d099 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -1750,7 +1750,7 @@ RETURN_CODE WCMD_goto(void) if (lstrcmpiW(L":eof", param1) == 0) { context->file_position.QuadPart = WCMD_FILE_POSITION_EOF; - return RETURN_CODE_ABORTED; + return RETURN_CODE_GOTO; } h = CreateFileW(context->batch_file->path_name, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); @@ -1768,7 +1768,7 @@ RETURN_CODE WCMD_goto(void)
ret = WCMD_find_label(h, paramStart, &context->file_position); CloseHandle(h); - if (ret) return RETURN_CODE_ABORTED; + if (ret) return RETURN_CODE_GOTO; WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET)); context->file_position.QuadPart = WCMD_FILE_POSITION_EOF; } diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 35ef48a70fe..4a1a462079d 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -698,7 +698,7 @@ SUCCESS 0 FAILURE 1 FAILURE 1 FAILURE 457 -@todo_wine@SUCCESS 0 +SUCCESS 0 FAILURE 459 FAILURE 460 FAILURE 461 diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index 7a1c2e50425..ae4dc038e34 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -147,7 +147,14 @@ struct _DIRECTORY_STACK *WCMD_dir_stack_free(struct _DIRECTORY_STACK *dir); typedef int RETURN_CODE; #define RETURN_CODE_SYNTAX_ERROR 255 #define RETURN_CODE_CANT_LAUNCH 9009 -#define RETURN_CODE_ABORTED (-999999) +#define RETURN_CODE_ABORTED (-999999) /* generated for exit /b so that all loops (and al.) are exited*/ +#define RETURN_CODE_GOTO (-999998) /* generated when changing file position (and break from if/for instructions) */ +#define RETURN_CODE_EXITED (-999997) /* generated when batch file terminates because child has terminated */ +/* to test if one shall break from instruction within a batch file */ +static inline BOOL WCMD_is_break(RETURN_CODE return_code) +{ + return return_code == RETURN_CODE_ABORTED || return_code == RETURN_CODE_GOTO; +}
BOOL WCMD_print_volume_information(const WCHAR *);
diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index d36a36d67aa..af81fb1bc64 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -1832,10 +1832,6 @@ static RETURN_CODE run_command_file(const WCHAR *file, WCHAR *full_cmdline) BOOL prev_echo_mode = echo_mode;
return_code = WCMD_call_batch(file, full_cmdline); - if (return_code == RETURN_CODE_ABORTED) - return_code = errorlevel; - else if (return_code != NO_ERROR) - errorlevel = return_code;
if (!context) echo_mode = prev_echo_mode; @@ -2364,6 +2360,17 @@ static RETURN_CODE execute_single_command(const WCHAR *command) { TRACE("Batch completed, but was not 'called' so skipping outer batch too\n"); context->file_position.QuadPart = WCMD_FILE_POSITION_EOF; + if (return_code == RETURN_CODE_ABORTED) + return_code = RETURN_CODE_EXITED; + } + else + { + if (return_code == RETURN_CODE_ABORTED || return_code == RETURN_CODE_EXITED) + return_code = errorlevel; + else if (return_code == RETURN_CODE_GOTO) + return_code = NO_ERROR; + else if (return_code != NO_ERROR) + errorlevel = return_code; } } else @@ -2385,7 +2392,13 @@ RETURN_CODE WCMD_call_command(WCHAR *command) { if (!*sc.path) return NO_ERROR; if (sc.is_command_file) + { return_code = run_command_file(sc.path, command); + if (WCMD_is_break(return_code)) + return_code = errorlevel; + else if (return_code != NO_ERROR) + errorlevel = return_code; + } else return_code = run_external_full_path(sc.path, command); return return_code; @@ -3836,7 +3849,7 @@ static RETURN_CODE for_control_execute_from_FILE(CMD_FOR_CONTROL *for_ctrl, FILE RETURN_CODE return_code = NO_ERROR;
/* Read line by line until end of file */ - while (return_code != RETURN_CODE_ABORTED && fgetws(buffer, ARRAY_SIZE(buffer), input)) + while (!WCMD_is_break(return_code) && fgetws(buffer, ARRAY_SIZE(buffer), input)) { size_t len;
@@ -3904,7 +3917,7 @@ static RETURN_CODE for_control_execute_fileset(CMD_FOR_CONTROL *for_ctrl, CMD_NO } else { - for (i = 0; return_code != RETURN_CODE_ABORTED; i++) + for (i = 0; !WCMD_is_break(return_code); i++) { WCHAR *element = WCMD_parameter(args, i, NULL, TRUE, FALSE); if (!element || !*element) break; @@ -3945,7 +3958,7 @@ static RETURN_CODE for_control_execute_set(CMD_FOR_CONTROL *for_ctrl, const WCHA
wcscpy(set, for_ctrl->set); handleExpansion(set, TRUE); - for (i = 0; return_code != RETURN_CODE_ABORTED; i++) + for (i = 0; !WCMD_is_break(return_code); i++) { WCHAR *element = WCMD_parameter(set, i, NULL, TRUE, FALSE); if (!element || !*element) break; @@ -3981,7 +3994,7 @@ static RETURN_CODE for_control_execute_set(CMD_FOR_CONTROL *for_ctrl, const WCHA wcscpy(&buffer[insert_pos], fd.cFileName); WCMD_set_for_loop_variable(for_ctrl->variable_index, buffer); return_code = node_execute(node); - } while (return_code != RETURN_CODE_ABORTED && FindNextFileW(hff, &fd) != 0); + } while (!WCMD_is_break(return_code) && FindNextFileW(hff, &fd) != 0); FindClose(hff); } else @@ -4010,7 +4023,7 @@ static RETURN_CODE for_control_execute_walk_files(CMD_FOR_CONTROL *for_ctrl, CMD else dirs_to_walk = WCMD_dir_stack_create(NULL, NULL); ref_len = wcslen(dirs_to_walk->dirName);
- while (return_code != RETURN_CODE_ABORTED && dirs_to_walk) + while (!WCMD_is_break(return_code) && dirs_to_walk) { TRACE("About to walk %p %ls for %s\n", dirs_to_walk, dirs_to_walk->dirName, debugstr_for_control(for_ctrl)); if (for_ctrl->flags & CMD_FOR_FLAG_TREE_RECURSE) @@ -4054,7 +4067,7 @@ static RETURN_CODE for_control_execute_numbers(CMD_FOR_CONTROL *for_ctrl, CMD_NO }
for (var = numbers[0]; - return_code != RETURN_CODE_ABORTED && ((numbers[1] < 0) ? var >= numbers[2] : var <= numbers[2]); + !WCMD_is_break(return_code) && ((numbers[1] < 0) ? var >= numbers[2] : var <= numbers[2]); var += numbers[1]) { WCHAR tmp[32]; @@ -4122,7 +4135,7 @@ RETURN_CODE node_execute(CMD_NODE *node) break; case CMD_CONCAT: return_code = node_execute(node->left); - if (return_code != RETURN_CODE_ABORTED) + if (!WCMD_is_break(return_code)) return_code = node_execute(node->right); break; case CMD_ONSUCCESS: @@ -4132,7 +4145,7 @@ RETURN_CODE node_execute(CMD_NODE *node) break; case CMD_ONFAILURE: return_code = node_execute(node->left); - if (return_code != NO_ERROR && return_code != RETURN_CODE_ABORTED) + if (return_code != NO_ERROR && !WCMD_is_break(return_code)) { /* that's needed for commands (POPD, RMDIR) that don't set errorlevel in case of failure. */ errorlevel = return_code; @@ -4175,7 +4188,7 @@ RETURN_CODE node_execute(CMD_NODE *node) if (errorlevel == RETURN_CODE_CANT_LAUNCH && saved_context) ExitProcess(255); return_code = ERROR_INVALID_FUNCTION; - if (return_code_left != RETURN_CODE_ABORTED && errorlevel != RETURN_CODE_CANT_LAUNCH) + if (!WCMD_is_break(return_code_left) && errorlevel != RETURN_CODE_CANT_LAUNCH) { HANDLE h = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_EXISTING, @@ -4505,10 +4518,11 @@ int __cdecl wmain (int argc, WCHAR *argvW[])
if (opt_c) { - RETURN_CODE return_code = WCMD_call_batch(NULL, cmd); - if (return_code != RETURN_CODE_ABORTED && return_code != NO_ERROR) - return return_code; - return errorlevel; + RETURN_CODE return_code = WCMD_call_batch(NULL, cmd); + if (return_code == RETURN_CODE_GOTO) return NO_ERROR; + if (return_code != RETURN_CODE_ABORTED && return_code != RETURN_CODE_EXITED && return_code != NO_ERROR) + return return_code; + return errorlevel; } GetStartupInfoW(&startupInfo); if (startupInfo.lpTitle != NULL)