This MR starts implementing pipe between commands with: - parallelism between the two sides of the pipe - using real pipes.
(Current code is sequential: running LHS, storing its output into a temp. file, then running RHS with temp. file as input).
The "interesting" part is: - in order to achieve parallelism, native cmd.exe when LHS or RHS of pipe contains a builtin command or instruction, run this part in a new and separate cmd.exe instance. - which means that we need to transform back the parsed structure of a tree of instructions into a string for feeding the new cmd.exe instance ('rebuild').
This serie mainly: - adds some tests to cover some quirks in pipe handling (esp. when expansion occurs between parent and child process), - refactors some code for the next patches, - introduce a new node in parse tree to help in the rebuild process (will be shown in next serie, and will allow matching the generated trailing spaces), - implement the pipe mechanism for "simple" commands.
Next serie will cover the non-"simple" commands.
Notes: - the now successful tests in the last patch of this serie are achieved because now the command string in expanded twice (once in parent cmd.exeprocess, and the second time, in child process). - all the wine_todo introduced in this serie will be solved in next serie.
From: Eric Pouech epouech@codeweavers.com
Complex meaning either FOR, IF or chaining operators (&, &&, || and |).
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/tests/test_builtins.cmd | 49 +++++++++++++++++++++++- programs/cmd/tests/test_builtins.cmd.exp | 36 +++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-)
diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd index 19dc3c788bb..7d0dc27b1e0 100644 --- a/programs/cmd/tests/test_builtins.cmd +++ b/programs/cmd/tests/test_builtins.cmd @@ -426,8 +426,6 @@ echo --- call :cfail r1||(call :cfail r2||call :cfail r3) echo --- chain pipe rem Piped commands run at the same time, so the print order varies. -rem Additionally, they don't run in the batch script context, as shown by -rem 'call :existing_label|echo read the error message'. (echo a 1>&2|echo a 1>&2) 2>&1 echo --- echo b1|echo b2 @@ -527,6 +525,53 @@ if 1==0 (echo o1) else echo o2&&echo o3 if 1==0 (echo p1) else echo p2||echo p3 echo --- if 1==0 (echo q1) else echo q2&echo q3 +echo ------------- Testing for variables expansion in pipes +rem * need to use @echo as subprocess doesn't inherit parent's echo on/off status +rem * .exp file uses @spaces@ (instead of individual @space@) as rebuilding command line +rem differs somehow between native & builtin +rem * need to turn echo off in lots of commands as 1) echo mode isn't inherited in subcmd +rem 2) builtin handling of @ on blocks is buggy +@(@for %%i in (a b) do @echo %%i)|more +echo --- +@(for %%i in (a b) do @for %%j in (c d) do @echo %%i-%%j)|more +echo --- +@(for %%i in (a b) do @for %%j in (a b) do @if %%i==%%j (@echo %%i same %%j) else (@echo %%i diff %%j))|more +echo --- +rem Show that builtin commands inside pipes are run in another context. +rem This can be seen too with 'call :existing_label|echo self' which error message +rem states call is invoked in non batch context. +set "WINE_VAR=foo" +echo a | set "WINE_VAR=bar" +echo %WINE_VAR% +set "WINE_VAR=foo" +set "WINE_VAR=bar" | set "=" +echo %WINE_VAR% +echo --- +rem delayed expansion is not inherited in child command, but applied to LHS/RHS +set "WINE_VAR=foo" +echo a | echo yy!WINE_VAR!yy +echo yy!WINE_VAR!yy | more +setlocal EnableDelayedExpansion +echo a | echo yy!WINE_VAR!yy +echo yy!WINE_VAR!yy | more +(echo a | echo yy!WINE_VAR!yy) | more +(echo yy!WINE_VAR!yy | more) | more +endlocal +echo --- +rem redirection of top level command is handled in current context +rem but redirection in subcommands in handled in child cmd process +mkdir foobarbar && cd foobarbar +set "WINE_VAR=foo" +setlocal EnableDelayedExpansion +echo bar1bar | more > !WINE_VAR! +type %WINE_VAR% +erase /q %WINE_VAR% +echo bar2bar | (more > !WINE_VAR!) +endlocal +setlocal DisableDelayedExpansion +if exist !WINE_VAR! (type !WINE_VAR!) else echo BADBAD +endlocal +cd .. && rmdir /s/q foobarbar echo ------------- Testing internal commands return codes setlocal EnableDelayedExpansion
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 644ca910c8c..db1894335f8 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -464,6 +464,42 @@ p2 --- q2 q3 +------------- Testing for variables expansion in pipes +@todo_wine@a@space@@space@ +@todo_wine@b@space@@space@ + +--- +@todo_wine@a-c@space@@space@ +@todo_wine@a-d@space@@space@ +@todo_wine@b-c@space@@space@ +@todo_wine@b-d@space@@space@ + +--- +@todo_wine@a same a@space@ +@todo_wine@a diff b@space@ +@todo_wine@b diff a@space@ +@todo_wine@b same b@space@ + +--- +@todo_wine@foo +@todo_wine@foo +--- +yy!WINE_VAR!yy +yy!WINE_VAR!yy@space@ + +yyfooyy +yyfooyy@space@ + +@todo_wine@yy!WINE_VAR!yy@space@ + +@todo_wine@yy!WINE_VAR!yy@space@ + + +--- +bar1bar@space@ + +@todo_wine@bar2bar@space@ +@todo_wine@ ------------- Testing internal commands return codes --- success/failure for basics SUCCESS 0
From: Eric Pouech epouech@codeweavers.com
Note: this will allow to keep track of explicit blocks.
Semantically speaking, a block can hold redirection and echo off ('@') attributes, so it will also allow to properly propagate and handle these attributes.
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/wcmd.h | 5 +++++ programs/cmd/wcmdmain.c | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+)
diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index 6810598142c..c14affb55ae 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -56,6 +56,7 @@ typedef enum _CMD_OPERATOR CMD_ONFAILURE, /* || */ CMD_ONSUCCESS, /* && */ CMD_PIPE, /* Single | */ + CMD_BLOCK, /* ( block ) */ CMD_IF, /* IF command */ CMD_FOR, /* FOR command */ } CMD_OPERATOR; @@ -131,6 +132,10 @@ typedef struct _CMD_NODE CMD_FOR_CONTROL for_ctrl; struct _CMD_NODE *do_block; }; + struct /* CMD_BLOCK */ + { + struct _CMD_NODE *block; + }; }; } CMD_NODE;
diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 46b89544221..efbc1da9704 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -1648,6 +1648,9 @@ void node_dispose_tree(CMD_NODE *node) for_control_dispose(&node->for_ctrl); node_dispose_tree(node->do_block); break; + case CMD_BLOCK: + node_dispose_tree(node->block); + break; } redirection_dispose_list(node->redirects); free(node); @@ -1701,6 +1704,17 @@ static CMD_NODE *node_create_for(CMD_FOR_CONTROL *for_ctrl, CMD_NODE *do_block) return new; }
+static CMD_NODE *node_create_block(CMD_NODE *block) +{ + CMD_NODE *new = xalloc(sizeof(CMD_NODE)); + + new->op = CMD_BLOCK; + new->block = block; + new->redirects = NULL; + + return new; +} + static void init_msvcrt_io_block(STARTUPINFOW* st) { STARTUPINFOW st_p; @@ -2810,6 +2824,7 @@ static BOOL node_builder_parse(struct node_builder *builder, unsigned precedence left = node_create_binary(CMD_CONCAT, left, right); } node_builder_consume(builder); + left = node_create_block(left); /* if we had redirection before '(', add them up front */ if (redir) { @@ -4231,6 +4246,9 @@ RETURN_CODE node_execute(CMD_NODE *node) case CMD_FOR: return_code = for_control_execute(&node->for_ctrl, node->do_block); break; + case CMD_BLOCK: + return_code = node_execute(node->block); + break; default: FIXME("Unexpected operator %u\n", node->op); return_code = ERROR_INVALID_FUNCTION;
From: Eric Pouech epouech@codeweavers.com
In order to separate process creation from waiting for process termination.
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/wcmdmain.c | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-)
diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index efbc1da9704..70d70d4e9cf 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -1767,14 +1767,13 @@ static void init_msvcrt_io_block(STARTUPINFOW* st) }
/* Attempt to open a file at a known path. */ -static RETURN_CODE run_external_full_path(const WCHAR *file, WCHAR *full_cmdline) +static RETURN_CODE spawn_external_full_path(const WCHAR *file, WCHAR *full_cmdline, HANDLE *handle, BOOL *cui_subsystem) { STARTUPINFOW si = {.cb = sizeof(si)}; - DWORD console, exit_code; + DWORD console; WCHAR exe_path[MAX_PATH]; PROCESS_INFORMATION pi; SHFILEINFOW psfi; - HANDLE handle; BOOL ret;
TRACE("%s\n", debugstr_w(file)); @@ -1791,7 +1790,7 @@ static RETURN_CODE run_external_full_path(const WCHAR *file, WCHAR *full_cmdline if (ret) { CloseHandle(pi.hThread); - handle = pi.hProcess; + *handle = pi.hProcess; } else { @@ -1813,21 +1812,34 @@ static RETURN_CODE run_external_full_path(const WCHAR *file, WCHAR *full_cmdline
if (ShellExecuteExW(&sei) && (INT_PTR)sei.hInstApp >= 32) { - handle = sei.hProcess; + *handle = sei.hProcess; } else { errorlevel = GetLastError(); - return errorlevel; + return ERROR_INVALID_FUNCTION; } }
- if (context || (console && !HIWORD(console))) - WaitForSingleObject(handle, INFINITE); - GetExitCodeProcess(handle, &exit_code); - errorlevel = (exit_code == STILL_ACTIVE) ? NO_ERROR : exit_code; + *cui_subsystem = console && !HIWORD(console); + return NO_ERROR; +}
- CloseHandle(handle); +static RETURN_CODE run_external_full_path(const WCHAR *file, WCHAR *full_cmdline) +{ + HANDLE handle; + BOOL waitable; + DWORD exit_code; + + if (spawn_external_full_path(file, full_cmdline, &handle, &waitable) == NO_ERROR) + { + if (context || waitable) + WaitForSingleObject(handle, INFINITE); + GetExitCodeProcess(handle, &exit_code); + errorlevel = (exit_code == STILL_ACTIVE) ? NO_ERROR : exit_code; + + CloseHandle(handle); + } return errorlevel; }
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/wcmdmain.c | 112 +++++++++++++++++++++------------------- 1 file changed, 59 insertions(+), 53 deletions(-)
diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 70d70d4e9cf..5f070598cd5 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -4156,6 +4156,64 @@ static RETURN_CODE for_control_execute(CMD_FOR_CONTROL *for_ctrl, CMD_NODE *node return return_code; }
+static RETURN_CODE handle_pipe_command(CMD_NODE *node) +{ + static SECURITY_ATTRIBUTES sa = {.nLength = sizeof(sa), .lpSecurityDescriptor = NULL, .bInheritHandle = TRUE}; + WCHAR temp_path[MAX_PATH]; + WCHAR filename[MAX_PATH]; + CMD_REDIRECTION *output; + HANDLE saved[3]; + struct batch_context *saved_context = context; + RETURN_CODE return_code; + + /* pipe LHS & RHS are run outside of any batch context */ + context = NULL; + /* FIXME: a real pipe instead of writing to an intermediate file would be + * better. + * But waiting for completion of commands will require more work. + */ + /* FIXME check precedence (eg foo > a | more) + * with following code, | has higher precedence than > a + * (which is likely wrong IIRC, and not what previous code was doing) + */ + /* Generate a unique temporary filename */ + GetTempPathW(ARRAY_SIZE(temp_path), temp_path); + GetTempFileNameW(temp_path, L"CMD", 0, filename); + TRACE("Using temporary file of %ls\n", filename); + + /* set output for left hand side command */ + output = redirection_create_file(REDIR_WRITE_TO, 1, filename); + if (push_std_redirections(output, saved)) + { + RETURN_CODE return_code_left = node_execute(node->left); + pop_std_redirections(saved); + + if (errorlevel == RETURN_CODE_CANT_LAUNCH && saved_context) + ExitProcess(255); + return_code = ERROR_INVALID_FUNCTION; + 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, + FILE_ATTRIBUTE_NORMAL, NULL); + if (h != INVALID_HANDLE_VALUE) + { + SetStdHandle(STD_INPUT_HANDLE, h); + return_code = node_execute(node->right); + if (errorlevel == RETURN_CODE_CANT_LAUNCH && saved_context) + ExitProcess(255); + } + } + DeleteFileW(filename); + errorlevel = return_code; + } + else return_code = ERROR_INVALID_FUNCTION; + redirection_dispose_list(output); + context = saved_context; + + return return_code; +} + RETURN_CODE node_execute(CMD_NODE *node) { HANDLE saved[3]; @@ -4195,59 +4253,7 @@ RETURN_CODE node_execute(CMD_NODE *node) } break; case CMD_PIPE: - { - static SECURITY_ATTRIBUTES sa = {.nLength = sizeof(sa), .lpSecurityDescriptor = NULL, .bInheritHandle = TRUE}; - WCHAR temp_path[MAX_PATH]; - WCHAR filename[MAX_PATH]; - CMD_REDIRECTION *output; - HANDLE saved[3]; - struct batch_context *saved_context = context; - - /* pipe LHS & RHS are run outside of any batch context */ - context = NULL; - /* FIXME: a real pipe instead of writing to an intermediate file would be - * better. - * But waiting for completion of commands will require more work. - */ - /* FIXME check precedence (eg foo > a | more) - * with following code, | has higher precedence than > a - * (which is likely wrong IIRC, and not what previous code was doing) - */ - /* Generate a unique temporary filename */ - GetTempPathW(ARRAY_SIZE(temp_path), temp_path); - GetTempFileNameW(temp_path, L"CMD", 0, filename); - TRACE("Using temporary file of %ls\n", filename); - - /* set output for left hand side command */ - output = redirection_create_file(REDIR_WRITE_TO, 1, filename); - if (push_std_redirections(output, saved)) - { - RETURN_CODE return_code_left = node_execute(node->left); - pop_std_redirections(saved); - - if (errorlevel == RETURN_CODE_CANT_LAUNCH && saved_context) - ExitProcess(255); - return_code = ERROR_INVALID_FUNCTION; - 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, - FILE_ATTRIBUTE_NORMAL, NULL); - if (h != INVALID_HANDLE_VALUE) - { - SetStdHandle(STD_INPUT_HANDLE, h); - return_code = node_execute(node->right); - if (errorlevel == RETURN_CODE_CANT_LAUNCH && saved_context) - ExitProcess(255); - } - } - DeleteFileW(filename); - errorlevel = return_code; - } - else return_code = ERROR_INVALID_FUNCTION; - redirection_dispose_list(output); - context = saved_context; - } + return_code = handle_pipe_command(node); break; case CMD_IF: if (if_condition_evaluate(&node->condition, &test))
From: Eric Pouech epouech@codeweavers.com
Note: all builtin commands in LHS or RHS of a pipe shall be run in a separate cmd instance (to provide concurrency between RHS and LHS).
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/tests/test_builtins.cmd.exp | 22 +-- programs/cmd/wcmdmain.c | 221 ++++++++++++++++++++++- 2 files changed, 230 insertions(+), 13 deletions(-)
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index db1894335f8..55768455b6d 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -481,8 +481,8 @@ q3 @todo_wine@b same b@space@
--- -@todo_wine@foo -@todo_wine@foo +foo +foo --- yy!WINE_VAR!yy yy!WINE_VAR!yy@space@ @@ -747,24 +747,24 @@ FAILURE 1 FAILURE 1 --------- success/failure when invoking cmd /k -------------- a -@todo_wine@ERRORLEVEL 0 +ERRORLEVEL 0 SUCCESS 0 -@todo_wine@ERRORLEVEL 1 +ERRORLEVEL 1 SUCCESS 0 -@todo_wine@ERRORLEVEL 1 +ERRORLEVEL 1 SUCCESS 0 FAILURE 457 -@todo_wine@ERRORLEVEL 458 +ERRORLEVEL 458 SUCCESS 0 -@todo_wine@ERRORLEVEL 459 +ERRORLEVEL 459 SUCCESS 0 -@todo_wine@ERRORLEVEL 460 +ERRORLEVEL 460 SUCCESS 0 -@todo_wine@ERRORLEVEL 461 +ERRORLEVEL 461 SUCCESS 0 -@todo_wine@ERRORLEVEL 0 +ERRORLEVEL 0 SUCCESS 0 -@todo_wine@ERRORLEVEL 9009 +ERRORLEVEL 9009 SUCCESS 0 @todo_wine@FAILURE 1 @todo_wine@------------ Testing 'set' ------------ diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 5f070598cd5..c16f1ebe32e 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -1821,7 +1821,7 @@ static RETURN_CODE spawn_external_full_path(const WCHAR *file, WCHAR *full_cmdli } }
- *cui_subsystem = console && !HIWORD(console); + if (cui_subsystem) *cui_subsystem = console && !HIWORD(console); return NO_ERROR; }
@@ -1957,6 +1957,7 @@ static RETURN_CODE search_command(WCHAR *command, struct search_command *sc, BOO /* Quick way to get the filename is to extract the first argument. */ firstParam = WCMD_parameter(command, 0, NULL, FALSE, TRUE);
+ sc->has_path = sc->has_extension = sc->is_command_file = FALSE; sc->cmd_index = WCMD_EXIT + 1;
if (!firstParam[0]) @@ -3257,6 +3258,94 @@ static WCHAR *fetch_next_line(BOOL first_line, WCHAR* buffer) return buffer; }
+struct command_rebuild +{ + WCHAR *buffer; + size_t buffer_size; + size_t pos; +}; + +struct rebuild_flags +{ + unsigned precedence : 3, + depth; +}; + +static BOOL rebuild_append(struct command_rebuild *rb, const WCHAR *toappend) +{ + size_t len = wcslen(toappend); + if (rb->pos + len >= rb->buffer_size) return FALSE; + wcscpy(rb->buffer + rb->pos, toappend); + rb->pos += len; + return TRUE; +} + +static BOOL rebuild_expand_and_append(struct command_rebuild *rb, const WCHAR *toappend, BOOL expand) +{ + WCHAR output[MAXSTRING]; + + if (!expand) return rebuild_append(rb, toappend); + /* it would be better to expand in place in rb, but handleExpansion doesn't take a string size */ + wcscpy(output, toappend); + handleExpansion(output, TRUE); + return rebuild_append(rb, output); +} + +static BOOL rebuild_insert(struct command_rebuild *rb, unsigned pos, const WCHAR *toinsert) +{ + size_t len = wcslen(toinsert); + if (rb->pos + len >= rb->buffer_size) return FALSE; + if (pos > rb->pos) return FALSE; + memmove(rb->buffer + pos + len, rb->buffer + pos, (rb->pos - pos + 1) * sizeof(WCHAR)); + memcpy(rb->buffer + pos, toinsert, len * sizeof(WCHAR)); + rb->pos += len; + return TRUE; +} + +static BOOL rebuild_append_redirection(struct command_rebuild *rb, const CMD_REDIRECTION *redir, BOOL expand) +{ + WCHAR number[2] = {L'0' + redir->fd, L'\0'}; + WCHAR clonestr[2] = {}; + const WCHAR *op; + const WCHAR *dst = redir->file; + + switch (redir->kind) + { + case REDIR_READ_FROM: + op = L"<"; + break; + case REDIR_WRITE_TO: + op = L">"; + break; + case REDIR_WRITE_APPEND: + op = L">>"; + break; + case REDIR_WRITE_CLONE: + op = L">&"; + clonestr[0] = L'0' + redir->clone; + dst = clonestr; + break; + default: return FALSE; + } + return rebuild_append(rb, number) && + rebuild_append(rb, op) && + rebuild_expand_and_append(rb, dst, expand); +} + +static BOOL rebuild_append_all_redirections(struct command_rebuild *rb, const CMD_NODE *node, BOOL expand) +{ + CMD_REDIRECTION *redir; + BOOL ret = TRUE; + + for (redir = node->redirects; ret && redir != NULL; redir = redir->next) + { + if (rb->pos && !iswspace(rb->buffer[rb->pos - 1])) + ret = ret && rebuild_append(rb, L" "); + ret = ret && rebuild_append_redirection(rb, redir, expand); + } + return ret; +} + static BOOL lexer_can_accept_do(const struct node_builder *builder) { unsigned d = 0; @@ -4156,7 +4245,7 @@ static RETURN_CODE for_control_execute(CMD_FOR_CONTROL *for_ctrl, CMD_NODE *node return return_code; }
-static RETURN_CODE handle_pipe_command(CMD_NODE *node) +static RETURN_CODE handle_pipe_command_old(CMD_NODE *node) { static SECURITY_ATTRIBUTES sa = {.nLength = sizeof(sa), .lpSecurityDescriptor = NULL, .bInheritHandle = TRUE}; WCHAR temp_path[MAX_PATH]; @@ -4214,6 +4303,134 @@ static RETURN_CODE handle_pipe_command(CMD_NODE *node) return return_code; }
+/* temp helper during migration */ +static BOOL can_run_new_pipe(CMD_NODE *node) +{ + switch (node->op) + { + case CMD_SINGLE: + return TRUE; + default: + return FALSE; + } +} + +static RETURN_CODE spawn_pipe_sub_command(CMD_NODE *node, HANDLE *child) +{ + WCHAR cmd_string[MAXSTRING]; + WCHAR comspec[MAX_PATH]; + struct command_rebuild rb = {cmd_string, ARRAY_SIZE(cmd_string), 0}; + RETURN_CODE return_code; + + switch (node->op) + { + case CMD_SINGLE: + { + struct search_command sc; + + /* command isn't delayed expanded... */ + return_code = search_command(node->command, &sc, TRUE); + if (return_code != NO_ERROR && sc.cmd_index == WCMD_EXIT + 1) + return RETURN_CODE_CANT_LAUNCH; + if ((sc.cmd_index <= WCMD_EXIT && (return_code != NO_ERROR || (!sc.has_path && !sc.has_extension))) || + (sc.has_path && sc.is_command_file)) + { + rebuild_expand_and_append(&rb, node->command, TRUE); + rebuild_append_all_redirections(&rb, node, TRUE); + } + else + { + HANDLE saved[3]; + + if (!push_std_redirections(node->redirects, saved)) + { + WCMD_print_error(); + return ERROR_INVALID_FUNCTION; + } + + return_code = spawn_external_full_path(sc.path, node->command, child, NULL); + pop_std_redirections(saved); + return return_code; + } + } + break; + default: + FIXME("Shouldn't happen\n"); + return ERROR_INVALID_FUNCTION; + } + + /* Any node except a single external command must be run in an alternate cmd.exe instance for concurrency. + * Native doesn't use COMSPEC for IF and FOR commands (likely for historical reasons as command.com + * didn't support these commands). + */ + if (node->op == CMD_IF || node->op == CMD_FOR || + !GetEnvironmentVariableW(L"COMSPEC", comspec, ARRAY_SIZE(comspec))) + { + if (!GetModuleFileNameW(NULL, comspec, ARRAY_SIZE(comspec))) + wcscpy(comspec, L"cmd.exe"); + } + + /* testings show that none of the options (extended commands, delayed expansions...) are passed as parameters */ + if (rebuild_insert(&rb, 0, L" /S /D /C "") && + rebuild_insert(&rb, 0, comspec) && + rebuild_append(&rb, L""")) + return_code = spawn_external_full_path(comspec, rb.buffer, child, NULL); + else + return_code = ERROR_INVALID_FUNCTION; + + return return_code; +} + +static RETURN_CODE handle_pipe_command(CMD_NODE *node) +{ + static SECURITY_ATTRIBUTES sa = {.nLength = sizeof(sa), .lpSecurityDescriptor = NULL, .bInheritHandle = TRUE}; + HANDLE lhs_child, rhs_child; + HANDLE read_pipe, write_pipe; + HANDLE saved_output; + RETURN_CODE return_code; + + if (!can_run_new_pipe(node->left) || !can_run_new_pipe(node->right)) + return handle_pipe_command_old(node); + + if (!CreatePipe(&read_pipe, &write_pipe, &sa, 0)) + return ERROR_INVALID_FUNCTION; + saved_output = GetStdHandle(STD_OUTPUT_HANDLE); + SetStdHandle(STD_OUTPUT_HANDLE, write_pipe); + return_code = spawn_pipe_sub_command(node->left, &lhs_child); + CloseHandle(write_pipe); + SetStdHandle(STD_OUTPUT_HANDLE, saved_output); + if (return_code == RETURN_CODE_CANT_LAUNCH && context) ExitProcess(255); + if (return_code == NO_ERROR) + { + SetStdHandle(STD_INPUT_HANDLE, read_pipe); + return_code = spawn_pipe_sub_command(node->right, &rhs_child); + if (return_code == NO_ERROR) + { + HANDLE h[2] = {lhs_child, rhs_child}; + DWORD exit_code, result; + /* We wait even on a GUI processes to terminate! + * but no direct ctrl-c support here (shall be inherited & handled by sub processes though) + */ + if ((result = WaitForMultipleObjects(ARRAY_SIZE(h), h, TRUE, INFINITE)) == WAIT_OBJECT_0) + { + if (!GetExitCodeProcess(rhs_child, &exit_code)) exit_code = 255; + return_code = exit_code; + } + else FIXME("Wait shouldn't fail %lx\n", result); + CloseHandle(rhs_child); + } + else + { + TerminateProcess(lhs_child, 255); + if (return_code == RETURN_CODE_CANT_LAUNCH && context) ExitProcess(255); + } + CloseHandle(lhs_child); + } + CloseHandle(read_pipe); + + return errorlevel = return_code; +} + RETURN_CODE node_execute(CMD_NODE *node) { HANDLE saved[3];