This is the fourth part in cmd.exe's engine rewrite.
It concerns: - start of decoupling parsing from execution by introducing ad hoc structure to hold parsing result to be passed for execution (done here for redirection, and if conditions), - refactor execution code with putting into helpers: + change of input/output streams + save / restore of input/output streams before / after execution
Note: - the handling of fd > 2 is clearly wrong, but it just mimics the current implementation. More work will be required afterwards (likely using directly CRT low level I/O), - I kept a few specific debug channels in place. They will be removed (or simplified at some point), but they could be useful to debug remaining issues.
-- v5: programs/cmd: Separate IF command parsing from execution. programs/cmd: Let errorlevel be a signed integer. programs/cmd: Create helper to execute a command. programs/cmd: Introduce structure CMD_REDIRECTION. programs/cmd: Introduce a helper to set std handles.
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/wcmdmain.c | 185 ++++++++++++++++++++-------------------- 1 file changed, 91 insertions(+), 94 deletions(-)
diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 2c5cf4a3196..764811fffd7 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -1295,6 +1295,83 @@ void WCMD_run_program (WCHAR *command, BOOL called)
}
+static BOOL set_std_redirections(WCHAR *new_redir, WCHAR *in_pipe) +{ + SECURITY_ATTRIBUTES sa = {.nLength = sizeof(sa), .lpSecurityDescriptor = NULL, .bInheritHandle = TRUE}; + WCHAR *pos, *redir; + HANDLE h; + + /* STDIN could come from a preceding pipe, so delete on close if it does */ + if (in_pipe) + { + TRACE("Input coming from %s\n", wine_dbgstr_w(in_pipe)); + h = CreateFileW(in_pipe, 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) return FALSE; + SetStdHandle(STD_INPUT_HANDLE, h); + /* Otherwise STDIN could come from a '<' redirect */ + } + else if ((pos = wcschr(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) return FALSE; + SetStdHandle(STD_INPUT_HANDLE, h); + } + + redir = new_redir; + /* Scan the whole command looking for > and 2> */ + while (redir != NULL && ((pos = wcschr(redir,'>')) != NULL)) + { + DWORD disposition; + int std_idx; + + if (pos > redir && (*(pos-1)=='2')) + std_idx = STD_ERROR_HANDLE; + else + /* FIXME what if a number different from 1 & 2 is present? */ + std_idx = STD_OUTPUT_HANDLE; + + pos++; + if ('>' == *pos) + { + disposition = OPEN_ALWAYS; + pos++; + } + else + disposition = CREATE_ALWAYS; + + /* Add support for 2>&1 */ + redir = pos; + if (*pos == '&') + { + int idx = *(pos+1) - '0'; + + /* FIXME what if a number different from 1 & 2 is present? */ + if (!DuplicateHandle(GetCurrentProcess(), + GetStdHandle(idx == 2 ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE), + GetCurrentProcess(), + &h, + 0, TRUE, DUPLICATE_SAME_ACCESS)) + { + FIXME("Duplicating handle failed with gle %ld\n", GetLastError()); + } + } + else + { + WCHAR *param = WCMD_parameter(pos, 0, NULL, FALSE, FALSE); + h = CreateFileW(param, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, + &sa, disposition, FILE_ATTRIBUTE_NORMAL, NULL); + if (h == INVALID_HANDLE_VALUE) return FALSE; + if (SetFilePointer(h, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER) + WCMD_print_error(); + } + SetStdHandle(std_idx, h); + } + return TRUE; +} + /***************************************************************************** * Process one command. If the command is EXIT this routine does not return. * We will recurse through here executing batch files. @@ -1305,21 +1382,16 @@ void WCMD_run_program (WCHAR *command, BOOL called) void WCMD_execute (const WCHAR *command, const WCHAR *redirects, CMD_NODE **cmdList, BOOL retrycall) { - WCHAR *cmd, *parms_start, *redir; - WCHAR *pos; + WCHAR *cmd, *parms_start; int status, i, cmd_index; - DWORD count, creationDisposition; - HANDLE h; + DWORD count; WCHAR *whichcmd; - SECURITY_ATTRIBUTES sa; WCHAR *new_cmd = NULL; WCHAR *new_redir = NULL; HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE), GetStdHandle (STD_OUTPUT_HANDLE), GetStdHandle (STD_ERROR_HANDLE)}; - DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE, - STD_OUTPUT_HANDLE, - STD_ERROR_HANDLE}; + static DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE}; BOOL prev_echo_mode, piped = FALSE;
WINE_TRACE("command on entry:%s (%p)\n", @@ -1332,7 +1404,6 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
/* Move copy of the redirects onto the heap so it can be expanded */ new_redir = xalloc(MAXSTRING * sizeof(WCHAR)); - redir = new_redir;
/* Strip leading whitespaces, and a '@' if supplied */ whichcmd = WCMD_skip_leading_spaces(cmd); @@ -1381,7 +1452,6 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects, } else { lstrcpyW(new_redir, redirects); } - /* Expand variables in command line mode only (batch mode will be expanded as the line is read in, except for 'for' loops) */ handleExpansion(new_cmd, (context != NULL), delayedsubst); @@ -1417,97 +1487,24 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects, return; }
- sa.nLength = sizeof(sa); - sa.lpSecurityDescriptor = NULL; - sa.bInheritHandle = TRUE; - /* * 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 && CMD_node_get_command(*cmdList)->pipeFile[0] != 0x00) { - WINE_TRACE("Input coming from %s\n", wine_dbgstr_w(CMD_node_get_command(*cmdList)->pipeFile)); - h = CreateFileW(CMD_node_get_command(*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 (); - free(cmd); - free(new_redir); - return; - } - SetStdHandle (STD_INPUT_HANDLE, h); - - /* No need to remember the temporary name any longer once opened */ - CMD_node_get_command(*cmdList)->pipeFile[0] = 0x00; - - /* Otherwise STDIN could come from a '<' redirect */ - } else if ((pos = wcschr(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 (); - free(cmd); - free(new_redir); - return; - } - SetStdHandle (STD_INPUT_HANDLE, h); - } - - /* Scan the whole command looking for > and 2> */ - while (redir != NULL && ((pos = wcschr(redir,'>')) != NULL)) { - int handle = 0; - - if (pos > redir && (*(pos-1)=='2')) - handle = 2; - else - handle = 1; - - pos++; - if ('>' == *pos) { - creationDisposition = OPEN_ALWAYS; - pos++; - } - else { - creationDisposition = CREATE_ALWAYS; - } - - /* 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 %ld\n", GetLastError()); - } - WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h); - - } 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 (); - free(cmd); - 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); - } - - SetStdHandle (idx_stdhandles[handle], h); + WCHAR *in_pipe = NULL; + if (cmdList && CMD_node_get_command(*cmdList)->pipeFile[0] != 0x00) + in_pipe = CMD_node_get_command(*cmdList)->pipeFile; + if (!set_std_redirections(new_redir, in_pipe)) { + WCMD_print_error (); + free(cmd); + free(new_redir); + return; } + if (in_pipe) + /* No need to remember the temporary name any longer once opened */ + in_pipe[0] = 0x00; } else { WINE_TRACE("Not touching redirects for a FOR or IF command\n"); }
From: Eric Pouech epouech@codeweavers.com
This structure will hold information for a redirection, and can be chained in a list when multiple redirections are listed.
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/batch.c | 2 +- programs/cmd/wcmd.h | 21 ++- programs/cmd/wcmdmain.c | 322 +++++++++++++++++++++++----------------- 3 files changed, 200 insertions(+), 145 deletions(-)
diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c index b68642fc29f..455cc99083c 100644 --- a/programs/cmd/batch.c +++ b/programs/cmd/batch.c @@ -93,7 +93,7 @@ void WCMD_batch (WCHAR *file, WCHAR *command, BOOL called, WCHAR *startLabel, HA the command as a result of a call failing to find a program, hence the retryCall parameter below is FALSE */ WCMD_process_commands(toExecute, FALSE, FALSE); - WCMD_free_commands(toExecute); + node_dispose_tree(toExecute); toExecute = NULL; } CloseHandle (h); diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index f538e4fc1a6..3ed98bcf73e 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -34,6 +34,19 @@ /* msdn specified max for Win XP */ #define MAXSTRING 8192
+/* Data structure to express a redirection */ +typedef struct _CMD_REDIRECTION +{ + enum CMD_REDIRECTION_KIND {REDIR_READ_FROM, REDIR_WRITE_TO, REDIR_WRITE_APPEND, REDIR_WRITE_CLONE} kind; + unsigned short fd; + struct _CMD_REDIRECTION *next; + union + { + unsigned short clone; /* only if kind is REDIR_WRITE_CLONE */ + WCHAR file[1]; /* only if kind is READ_FROM, WRITE or WRITE_APPEND */ + }; +} CMD_REDIRECTION; + /* Data structure to hold commands delimiters/separators */
typedef enum _CMD_OPERATOR @@ -50,7 +63,7 @@ typedef enum _CMD_OPERATOR typedef struct _CMD_COMMAND { WCHAR *command; /* Command string to execute */ - WCHAR *redirects; /* Redirects in place */ + CMD_REDIRECTION *redirects; /* Redirects in place */ int bracketDepth;/* How deep bracketing have we got to */ WCHAR pipeFile[MAX_PATH]; /* Where to get input from for pipes */ } CMD_COMMAND; @@ -153,9 +166,9 @@ BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars, LPDWO
WCHAR *WCMD_ReadAndParseLine(const WCHAR *initialcmd, CMD_NODE **output, HANDLE readFrom); CMD_NODE *WCMD_process_commands(CMD_NODE *thisCmd, BOOL oneBracket, BOOL retrycall); -void WCMD_free_commands(CMD_NODE *cmds); -void WCMD_execute (const WCHAR *orig_command, const WCHAR *redirects, - CMD_NODE **cmdList, BOOL retrycall); +void node_dispose_tree(CMD_NODE *cmds); +void WCMD_execute(const WCHAR *orig_command, CMD_REDIRECTION *redirects, + CMD_NODE **cmdList, BOOL retrycall);
void *xrealloc(void *, size_t) __WINE_ALLOC_SIZE(2) __WINE_DEALLOC(free);
diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 764811fffd7..1c5f10f7abd 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -946,6 +946,74 @@ static void WCMD_parse (const WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2) /* Data structures for commands */ /* ============================== */
+static void redirection_dispose_list(CMD_REDIRECTION *redir) +{ + while (redir) + { + CMD_REDIRECTION *next = redir->next; + free(redir); + redir = next; + } +} + +static CMD_REDIRECTION *redirection_create_file(enum CMD_REDIRECTION_KIND kind, unsigned fd, const WCHAR *file) +{ + size_t len = wcslen(file) + 1; + CMD_REDIRECTION *redir = xalloc(offsetof(CMD_REDIRECTION, file[len])); + + redir->kind = kind; + redir->fd = fd; + memcpy(redir->file, file, len * sizeof(WCHAR)); + redir->next = NULL; + + return redir; +} + +static CMD_REDIRECTION *redirection_create_clone(unsigned fd, unsigned fd_clone) +{ + CMD_REDIRECTION *redir = xalloc(sizeof(*redir)); + + redir->kind = REDIR_WRITE_CLONE; + redir->fd = fd; + redir->clone = fd_clone; + redir->next = NULL; + + return redir; +} + +static void command_dispose(CMD_COMMAND *cmd) +{ + if (cmd) + { + free(cmd->command); + redirection_dispose_list(cmd->redirects); + free(cmd); + } +} + +/*************************************************************************** + * node_dispose_tree + * + * Frees the storage held for a parsed command line + * - This is not done in the process_commands, as eventually the current + * pointer will be modified within the commands, and hence a single free + * routine is simpler + */ +void node_dispose_tree(CMD_NODE *cmds) +{ + /* Loop through the commands, freeing them one by one */ + while (cmds) + { + CMD_NODE *thisCmd = cmds; + cmds = CMD_node_next(cmds); + if (thisCmd->op == CMD_SINGLE) + command_dispose(thisCmd->command); + else + node_dispose_tree(thisCmd->left); + free(thisCmd); + } +} + static CMD_NODE *node_create_single(CMD_COMMAND *c) { CMD_NODE *new = xalloc(sizeof(CMD_NODE)); @@ -1280,7 +1348,7 @@ void WCMD_run_program (WCHAR *command, BOOL called) /* Parse the command string, without reading any more input */ WCMD_ReadAndParseLine(command, &toExecute, INVALID_HANDLE_VALUE); WCMD_process_commands(toExecute, FALSE, called); - WCMD_free_commands(toExecute); + node_dispose_tree(toExecute); toExecute = NULL; return; } @@ -1295,10 +1363,17 @@ void WCMD_run_program (WCHAR *command, BOOL called)
}
-static BOOL set_std_redirections(WCHAR *new_redir, WCHAR *in_pipe) +/* this is obviously wrong... will require more work to be fixed */ +static inline unsigned clamp_fd(unsigned fd) { - SECURITY_ATTRIBUTES sa = {.nLength = sizeof(sa), .lpSecurityDescriptor = NULL, .bInheritHandle = TRUE}; - WCHAR *pos, *redir; + return fd <= 2 ? fd : 1; +} + +static BOOL set_std_redirections(CMD_REDIRECTION *redir, WCHAR *in_pipe) +{ + static DWORD std_index[3] = {STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE}; + static SECURITY_ATTRIBUTES sa = {.nLength = sizeof(sa), .lpSecurityDescriptor = NULL, .bInheritHandle = TRUE}; + WCHAR expanded_filename[MAXSTRING]; HANDLE h;
/* STDIN could come from a preceding pipe, so delete on close if it does */ @@ -1312,62 +1387,53 @@ static BOOL set_std_redirections(WCHAR *new_redir, WCHAR *in_pipe) SetStdHandle(STD_INPUT_HANDLE, h); /* Otherwise STDIN could come from a '<' redirect */ } - else if ((pos = wcschr(new_redir,'<')) != NULL) + for (; redir; redir = redir->next) { - 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) return FALSE; - SetStdHandle(STD_INPUT_HANDLE, h); - } - - redir = new_redir; - /* Scan the whole command looking for > and 2> */ - while (redir != NULL && ((pos = wcschr(redir,'>')) != NULL)) - { - DWORD disposition; - int std_idx; - - if (pos > redir && (*(pos-1)=='2')) - std_idx = STD_ERROR_HANDLE; - else - /* FIXME what if a number different from 1 & 2 is present? */ - std_idx = STD_OUTPUT_HANDLE; - - pos++; - if ('>' == *pos) - { - disposition = OPEN_ALWAYS; - pos++; - } - else - disposition = CREATE_ALWAYS; - - /* Add support for 2>&1 */ - redir = pos; - if (*pos == '&') + switch (redir->kind) { - int idx = *(pos+1) - '0'; - - /* FIXME what if a number different from 1 & 2 is present? */ + case REDIR_READ_FROM: + if (in_pipe) continue; /* give precedence to pipe */ + wcscpy(expanded_filename, redir->file); + handleExpansion(expanded_filename, context != NULL, delayedsubst); + h = CreateFileW(expanded_filename, GENERIC_READ, FILE_SHARE_READ, + &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (h == INVALID_HANDLE_VALUE) + { + WARN("Failed to open (%ls)\n", expanded_filename); + return FALSE; + } + TRACE("Open (%ls) => %p\n", expanded_filename, h); + break; + case REDIR_WRITE_TO: + case REDIR_WRITE_APPEND: + { + DWORD disposition = redir->kind == REDIR_WRITE_TO ? CREATE_ALWAYS : OPEN_ALWAYS; + wcscpy(expanded_filename, redir->file); + handleExpansion(expanded_filename, context != NULL, delayedsubst); + h = CreateFileW(expanded_filename, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, + &sa, disposition, FILE_ATTRIBUTE_NORMAL, NULL); + if (h == INVALID_HANDLE_VALUE) + { + WARN("Failed to open (%ls)\n", expanded_filename); + return FALSE; + } + TRACE("Open %u (%ls) => %p\n", redir->fd, expanded_filename, h); + if (SetFilePointer(h, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER) + WCMD_print_error(); + } + break; + case REDIR_WRITE_CLONE: if (!DuplicateHandle(GetCurrentProcess(), - GetStdHandle(idx == 2 ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE), + GetStdHandle(std_index[clamp_fd(redir->clone)]), GetCurrentProcess(), &h, 0, TRUE, DUPLICATE_SAME_ACCESS)) { - FIXME("Duplicating handle failed with gle %ld\n", GetLastError()); + WARN("Duplicating handle failed with gle %ld\n", GetLastError()); } + break; } - else - { - WCHAR *param = WCMD_parameter(pos, 0, NULL, FALSE, FALSE); - h = CreateFileW(param, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, - &sa, disposition, FILE_ATTRIBUTE_NORMAL, NULL); - if (h == INVALID_HANDLE_VALUE) return FALSE; - if (SetFilePointer(h, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER) - WCMD_print_error(); - } - SetStdHandle(std_idx, h); + SetStdHandle(std_index[clamp_fd(redir->fd)], h); } return TRUE; } @@ -1379,20 +1445,20 @@ static BOOL set_std_redirections(WCHAR *new_redir, WCHAR *in_pipe) * try to run it as an internal command. 'retrycall' represents whether * we are attempting this retry. */ -void WCMD_execute (const WCHAR *command, const WCHAR *redirects, - CMD_NODE **cmdList, BOOL retrycall) +void WCMD_execute(const WCHAR *command, CMD_REDIRECTION *redirects, + CMD_NODE **cmdList, BOOL retrycall) { WCHAR *cmd, *parms_start; int status, i, cmd_index; DWORD count; WCHAR *whichcmd; WCHAR *new_cmd = NULL; - WCHAR *new_redir = NULL; HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE), GetStdHandle (STD_OUTPUT_HANDLE), GetStdHandle (STD_ERROR_HANDLE)}; static DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE}; BOOL prev_echo_mode, piped = FALSE; + CMD_REDIRECTION *piped_redir;
WINE_TRACE("command on entry:%s (%p)\n", wine_dbgstr_w(command), cmdList); @@ -1402,9 +1468,6 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects, lstrcpyW(new_cmd, command); cmd = new_cmd;
- /* Move copy of the redirects onto the heap so it can be expanded */ - new_redir = xalloc(MAXSTRING * sizeof(WCHAR)); - /* Strip leading whitespaces, and a '@' if supplied */ whichcmd = WCMD_skip_leading_spaces(cmd); WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd)); @@ -1447,15 +1510,15 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
/* If piped output, send stdout to the pipe by appending >filename to redirects */ if (piped) { - wsprintfW (new_redir, L"%s > %s", redirects, CMD_node_get_command(CMD_node_next(*cmdList))->pipeFile); - WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir)); + const WCHAR *to = CMD_node_get_command(CMD_node_next(*cmdList))->pipeFile; + piped_redir = redirection_create_file(REDIR_WRITE_TO, 1, to); + piped_redir->next = redirects; } else { - lstrcpyW(new_redir, redirects); + piped_redir = redirects; } /* Expand variables in command line mode only (batch mode will be expanded as the line is read in, except for 'for' loops) */ handleExpansion(new_cmd, (context != NULL), delayedsubst); - handleExpansion(new_redir, (context != NULL), delayedsubst);
/* * Changing default drive has to be handled as a special case, anything @@ -1482,9 +1545,7 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects, WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd)); status = SetCurrentDirectoryW(cmd); if (!status) WCMD_print_error (); - free(cmd); - free(new_redir); - return; + goto cleanup; }
/* @@ -1496,11 +1557,9 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects, WCHAR *in_pipe = NULL; if (cmdList && CMD_node_get_command(*cmdList)->pipeFile[0] != 0x00) in_pipe = CMD_node_get_command(*cmdList)->pipeFile; - if (!set_std_redirections(new_redir, in_pipe)) { + if (!set_std_redirections(piped_redir, in_pipe)) { WCMD_print_error (); - free(cmd); - free(new_redir); - return; + goto cleanup; } if (in_pipe) /* No need to remember the temporary name any longer once opened */ @@ -1663,8 +1722,9 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects, WCMD_run_program (whichcmd, FALSE); echo_mode = prev_echo_mode; } +cleanup: free(cmd); - free(new_redir); + if (piped_redir != redirects) free(piped_redir);
/* Restore old handles */ for (i=0; i<3; i++) { @@ -1690,34 +1750,11 @@ WCHAR *WCMD_LoadMessage(UINT id) { return msg; }
-static const char *op2str(CMD_OPERATOR op) -{ - static const char* optable[] = {"op-single", "op-&", "op-||", "op-&&", "op-|"}; - if (op < ARRAY_SIZE(optable)) return optable[op]; - return "op-unk"; -} - -/*************************************************************************** - * WCMD_DumpCommands - * - * Dumps out the parsed command line to ensure syntax is correct - */ -static void WCMD_DumpCommands(CMD_NODE *commands) +static WCHAR *find_chr(WCHAR *in, WCHAR *last, const WCHAR *delims) { - CMD_NODE *thisCmd = commands; - - WINE_TRACE("Parsed line:\n"); - while (thisCmd != NULL) - { - TRACE("%p %2.2d %p %s Redir:%s %s\n", - thisCmd, - CMD_node_get_depth(thisCmd), - CMD_node_next(thisCmd), - wine_dbgstr_w(CMD_node_get_command(thisCmd)->command), - wine_dbgstr_w(CMD_node_get_command(thisCmd)->redirects), - op2str(thisCmd->op)); - thisCmd = CMD_node_next(thisCmd); - } + for (; in < last; in++) + if (wcschr(delims, *in)) return in; + return NULL; }
/*************************************************************************** @@ -1737,15 +1774,52 @@ static CMD_COMMAND *WCMD_createCommand(WCHAR *command, int *commandLen,
/* Copy in the command */ if (command) { + WCHAR *pos; + WCHAR *last = redirs + *redirLen; + CMD_REDIRECTION **insrt; + thisEntry->command = xalloc((*commandLen + 1) * sizeof(WCHAR)); memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR)); thisEntry->command[*commandLen] = 0x00;
- /* Copy in the redirects */ - thisEntry->redirects = xalloc((*redirLen + 1) * sizeof(WCHAR)); - memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR)); - thisEntry->redirects[*redirLen] = 0x00; - thisEntry->pipeFile[0] = 0x00; + if (redirs) redirs[*redirLen] = 0; + /* Create redirects, keeping order (eg "2>foo 1>&2") */ + insrt = &thisEntry->redirects; + *insrt = NULL; + for (pos = redirs; pos; insrt = &(*insrt)->next) + { + WCHAR *p = find_chr(pos, last, L"<>"); + WCHAR *filename; + + if (!p) break; + + if (*p == L'<') + { + filename = WCMD_parameter(p + 1, 0, NULL, FALSE, FALSE); + handleExpansion(filename, context != NULL, FALSE); + *insrt = redirection_create_file(REDIR_READ_FROM, 0, filename); + } + else + { + unsigned fd = 1; + unsigned op = REDIR_WRITE_TO; + + if (p > redirs && p[-1] >= L'2' && p[-1] <= L'9') fd = p[-1] - L'0'; + if (*++p == L'>') {p++; op = REDIR_WRITE_APPEND;} + if (*p == L'&' && (p[1] >= L'0' && p[1] <= L'9')) + { + *insrt = redirection_create_clone(fd, p[1] - '0'); + p++; + } + else + { + filename = WCMD_parameter(p, 0, NULL, FALSE, FALSE); + handleExpansion(filename, context != NULL, FALSE); + *insrt = redirection_create_file(op, fd, filename); + } + } + pos = p + 1; + }
/* Reset the lengths */ *commandLen = 0; @@ -1756,10 +1830,10 @@ static CMD_COMMAND *WCMD_createCommand(WCHAR *command, int *commandLen, } else { thisEntry->command = NULL; thisEntry->redirects = NULL; - thisEntry->pipeFile[0] = 0x00; }
/* Fill in other fields */ + thisEntry->pipeFile[0] = 0x00; thisEntry->bracketDepth = curDepth; return thisEntry; } @@ -1901,6 +1975,7 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_NODE **output, HANDLE return NULL; } curPos = extraSpace; + TRACE("About to parse line (%ls)\n", extraSpace);
/* Handle truncated input - issue warning */ if (lstrlenW(extraSpace) == MAXSTRING -1) { @@ -2381,14 +2456,11 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_NODE **output, HANDLE
if (curDepth > lineCurDepth) { WINE_TRACE("Brackets do not match, error out without executing.\n"); - WCMD_free_commands(*output); + node_dispose_tree(*output); *output = NULL; errorlevel = 255; }
- /* Dump out the parsed output */ - WCMD_DumpCommands(*output); - return extraSpace; }
@@ -2423,7 +2495,7 @@ CMD_NODE *WCMD_process_commands(CMD_NODE *thisCmd, BOOL oneBracket, Also, skip over any batch labels (eg. :fred) */ if (CMD_node_get_command(thisCmd)->command && CMD_node_get_command(thisCmd)->command[0] != ':') { WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(CMD_node_get_command(thisCmd)->command)); - WCMD_execute (CMD_node_get_command(thisCmd)->command, CMD_node_get_command(thisCmd)->redirects, &thisCmd, retrycall); + WCMD_execute(CMD_node_get_command(thisCmd)->command, CMD_node_get_command(thisCmd)->redirects, &thisCmd, retrycall); }
/* Step on unless the command itself already stepped on */ @@ -2432,36 +2504,6 @@ CMD_NODE *WCMD_process_commands(CMD_NODE *thisCmd, BOOL oneBracket, return NULL; }
-static void WCMD_free_command(CMD_COMMAND *cmd) -{ - free(cmd->command); - free(cmd->redirects); - free(cmd); -} - -/*************************************************************************** - * WCMD_free_commands - * - * Frees the storage held for a parsed command line - * - This is not done in the process_commands, as eventually the current - * pointer will be modified within the commands, and hence a single free - * routine is simpler - */ -void WCMD_free_commands(CMD_NODE *cmds) -{ - /* Loop through the commands, freeing them one by one */ - while (cmds) - { - CMD_NODE *thisCmd = cmds; - cmds = CMD_node_next(cmds); - if (thisCmd->op == CMD_SINGLE) - WCMD_free_command(thisCmd->command); - else - WCMD_free_commands(thisCmd->left); - free(thisCmd); - } -} - static BOOL WINAPI my_event_handler(DWORD ctrl) { WCMD_output(L"\n"); @@ -2738,7 +2780,7 @@ int __cdecl wmain (int argc, WCHAR *argvW[]) /* Parse the command string, without reading any more input */ WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE); WCMD_process_commands(toExecute, FALSE, FALSE); - WCMD_free_commands(toExecute); + node_dispose_tree(toExecute); toExecute = NULL;
free(cmd); @@ -2819,7 +2861,7 @@ int __cdecl wmain (int argc, WCHAR *argvW[]) /* Parse the command string, without reading any more input */ WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE); WCMD_process_commands(toExecute, FALSE, FALSE); - WCMD_free_commands(toExecute); + node_dispose_tree(toExecute); toExecute = NULL; free(cmd); } @@ -2838,7 +2880,7 @@ int __cdecl wmain (int argc, WCHAR *argvW[]) if (!WCMD_ReadAndParseLine(NULL, &toExecute, GetStdHandle(STD_INPUT_HANDLE))) break; WCMD_process_commands(toExecute, FALSE, FALSE); - WCMD_free_commands(toExecute); + node_dispose_tree(toExecute); promptNewLine = !!toExecute; toExecute = NULL; }
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/wcmdmain.c | 183 ++++++++++++++++++++++++---------------- 1 file changed, 111 insertions(+), 72 deletions(-)
diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 1c5f10f7abd..1a37e93ccc6 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -1445,23 +1445,15 @@ static BOOL set_std_redirections(CMD_REDIRECTION *redir, WCHAR *in_pipe) * try to run it as an internal command. 'retrycall' represents whether * we are attempting this retry. */ -void WCMD_execute(const WCHAR *command, CMD_REDIRECTION *redirects, - CMD_NODE **cmdList, BOOL retrycall) +static void execute_single_command(const WCHAR *command, CMD_NODE **cmdList, BOOL retrycall) { WCHAR *cmd, *parms_start; - int status, i, cmd_index; - DWORD count; + int status, cmd_index, count; WCHAR *whichcmd; WCHAR *new_cmd = NULL; - HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE), - GetStdHandle (STD_OUTPUT_HANDLE), - GetStdHandle (STD_ERROR_HANDLE)}; - static DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE}; - BOOL prev_echo_mode, piped = FALSE; - CMD_REDIRECTION *piped_redir; + BOOL prev_echo_mode;
- WINE_TRACE("command on entry:%s (%p)\n", - wine_dbgstr_w(command), cmdList); + 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 = xalloc(MAXSTRING * sizeof(WCHAR)); @@ -1470,7 +1462,7 @@ void WCMD_execute(const WCHAR *command, CMD_REDIRECTION *redirects,
/* Strip leading whitespaces, and a '@' if supplied */ whichcmd = WCMD_skip_leading_spaces(cmd); - WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd)); + TRACE("Command: '%s'\n", wine_dbgstr_w(cmd)); if (whichcmd[0] == '@') whichcmd++;
/* Check if the command entered is internal, and identify which one */ @@ -1484,40 +1476,6 @@ void WCMD_execute(const WCHAR *command, CMD_REDIRECTION *redirects, } 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 (!(cmd_index == WCMD_FOR || cmd_index == WCMD_IF) && - cmdList && (*cmdList)->op == CMD_PIPE) { - - WCHAR temp_path[MAX_PATH]; - - /* Remember piping is in action */ - WINE_TRACE("Output needs to be piped\n"); - piped = TRUE; - - /* Generate a unique temporary filename */ - GetTempPathW(ARRAY_SIZE(temp_path), temp_path); - GetTempFileNameW(temp_path, L"CMD", 0, CMD_node_get_command(CMD_node_next(*cmdList))->pipeFile); - WINE_TRACE("Using temporary file of %s\n", - wine_dbgstr_w(CMD_node_get_command(CMD_node_next(*cmdList))->pipeFile)); - } - - /* If piped output, send stdout to the pipe by appending >filename to redirects */ - if (piped) { - const WCHAR *to = CMD_node_get_command(CMD_node_next(*cmdList))->pipeFile; - piped_redir = redirection_create_file(REDIR_WRITE_TO, 1, to); - piped_redir->next = redirects; - } else { - piped_redir = redirects; - } - /* Expand variables in command line mode only (batch mode will - be expanded as the line is read in, except for 'for' loops) */ handleExpansion(new_cmd, (context != NULL), delayedsubst);
/* @@ -1548,27 +1506,8 @@ void WCMD_execute(const WCHAR *command, CMD_REDIRECTION *redirects, goto cleanup; }
- /* - * 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)) { - WCHAR *in_pipe = NULL; - if (cmdList && CMD_node_get_command(*cmdList)->pipeFile[0] != 0x00) - in_pipe = CMD_node_get_command(*cmdList)->pipeFile; - if (!set_std_redirections(piped_redir, in_pipe)) { - WCMD_print_error (); - goto cleanup; - } - if (in_pipe) - /* No need to remember the temporary name any longer once opened */ - in_pipe[0] = 0x00; - } else { - WINE_TRACE("Not touching redirects for a FOR or IF command\n"); - } WCMD_parse (parms_start, quals, param1, param2); - WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2)); + TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
if (cmd_index <= WCMD_EXIT && (parms_start[0] == '/') && (parms_start[1] == '?')) { /* this is a help request for a builtin program */ @@ -1722,16 +1661,116 @@ void WCMD_execute(const WCHAR *command, CMD_REDIRECTION *redirects, WCMD_run_program (whichcmd, FALSE); echo_mode = prev_echo_mode; } +cleanup: + free(cmd); +} + +/***************************************************************************** + * Process one command. If the command is EXIT this routine does not return. + * We will recurse through here executing batch files. + * Note: If call is used to a non-existing program, we reparse the line and + * try to run it as an internal command. 'retrycall' represents whether + * we are attempting this retry. + */ +void WCMD_execute(const WCHAR *command, CMD_REDIRECTION *redirects, + CMD_NODE **cmdList, BOOL retrycall) +{ + WCHAR *cmd; + int i, cmd_index, count; + WCHAR *whichcmd; + WCHAR *new_cmd = NULL; + HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE), + GetStdHandle (STD_OUTPUT_HANDLE), + GetStdHandle (STD_ERROR_HANDLE)}; + static DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE}; + CMD_REDIRECTION *piped_redir = redirects; + + 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 = xalloc(MAXSTRING * sizeof(WCHAR)); + lstrcpyW(new_cmd, command); + cmd = new_cmd; + + /* Strip leading whitespaces, and a '@' if supplied */ + whichcmd = WCMD_skip_leading_spaces(cmd); + 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 (cmd_index=0; cmd_index<=WCMD_EXIT; cmd_index++) { + if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, + whichcmd, count, inbuilt[cmd_index], -1) == CSTR_EQUAL) break; + } + + /* 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 */ + /* FIXME this is wrong: we need to discriminate between redirection in individual + * commands in the blocks, vs redirection of the whole command. + */ + if (!(cmd_index == WCMD_FOR || cmd_index == WCMD_IF) && + cmdList && (*cmdList)->op == CMD_PIPE) + { + const WCHAR *to; + WCHAR temp_path[MAX_PATH]; + + /* Remember piping is in action */ + TRACE("Output needs to be piped\n"); + + /* Generate a unique temporary filename */ + GetTempPathW(ARRAY_SIZE(temp_path), temp_path); + GetTempFileNameW(temp_path, L"CMD", 0, CMD_node_get_command(CMD_node_next(*cmdList))->pipeFile); + TRACE("Using temporary file of %s\n", + wine_dbgstr_w(CMD_node_get_command(CMD_node_next(*cmdList))->pipeFile)); + /* send stdout to the pipe by appending >filename to redirects */ + to = CMD_node_get_command(CMD_node_next(*cmdList))->pipeFile; + piped_redir = redirection_create_file(REDIR_WRITE_TO, 1, to); + piped_redir->next = redirects; + } + + /* + * 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. + */ + /* FIXME this is wrong (see above) */ + if (!(cmd_index == WCMD_FOR || cmd_index == WCMD_IF)) { + WCHAR *in_pipe = NULL; + if (cmdList && CMD_node_get_command(*cmdList)->pipeFile[0] != 0x00) + in_pipe = CMD_node_get_command(*cmdList)->pipeFile; + if (!set_std_redirections(piped_redir, in_pipe)) { + WCMD_print_error (); + goto cleanup; + } + if (in_pipe) + /* No need to remember the temporary name any longer once opened */ + in_pipe[0] = 0x00; + } else { + TRACE("Not touching redirects for a FOR or IF command\n"); + } + execute_single_command(command, cmdList, retrycall); cleanup: free(cmd); if (piped_redir != redirects) free(piped_redir);
/* Restore old handles */ - for (i=0; i<3; i++) { - if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) { - CloseHandle (GetStdHandle (idx_stdhandles[i])); - SetStdHandle (idx_stdhandles[i], old_stdhandles[i]); - } + for (i = 0; i < 3; i++) + { + if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) + { + CloseHandle(GetStdHandle (idx_stdhandles[i])); + SetStdHandle(idx_stdhandles[i], old_stdhandles[i]); + } } }
From: Eric Pouech epouech@codeweavers.com
Manual testing show that native reports errorlevel as a signed entity.
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/builtins.c | 13 +++++++------ programs/cmd/tests/test_builtins.cmd | 2 ++ programs/cmd/tests/test_builtins.cmd.exp | 1 + programs/cmd/wcmd.h | 2 +- programs/cmd/wcmdmain.c | 8 ++++---- 5 files changed, 15 insertions(+), 11 deletions(-)
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index bc9ba9e5f11..b4c00de1e2b 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -402,7 +402,7 @@ void WCMD_choice (const WCHAR * args) { SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
errorlevel = (ptr - opt_c) + 1; - WINE_TRACE("answer: %ld\n", errorlevel); + TRACE("answer: %d\n", errorlevel); free(my_command); return; } @@ -2836,9 +2836,9 @@ int evaluate_if_condition(WCHAR *p, WCHAR **command, int *test, int *negate) if (!lstrcmpiW(condition, L"errorlevel")) { WCHAR *param = WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE); WCHAR *endptr; - long int param_int = wcstol(param, &endptr, 10); - if (*endptr) goto syntax_err; - *test = ((long int)errorlevel >= param_int); + int param_int = wcstol(param, &endptr, 10); + if (endptr == param || *endptr) goto syntax_err; + *test = (errorlevel >= param_int); WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE); } else if (!lstrcmpiW(condition, L"exist")) { @@ -4483,9 +4483,10 @@ void WCMD_start(WCHAR *args)
if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi )) { + DWORD exit_code; WaitForSingleObject( pi.hProcess, INFINITE ); - GetExitCodeProcess( pi.hProcess, &errorlevel ); - if (errorlevel == STILL_ACTIVE) errorlevel = 0; + GetExitCodeProcess( pi.hProcess, &exit_code ); + errorlevel = (exit_code == STILL_ACTIVE) ? 0 : exit_code; CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd index 8fa724c2682..02020a5a6ef 100644 --- a/programs/cmd/tests/test_builtins.cmd +++ b/programs/cmd/tests/test_builtins.cmd @@ -3310,6 +3310,8 @@ should_not_exist 2> nul > nul echo %ErrorLevel% rem nt 4.0 doesn't really support a way of setting errorlevel, so this is weak rem See http://www.robvanderwoude.com/exit.php +call :setError -9999 +echo %ErrorLevel% call :setError 1 echo %ErrorLevel% if errorlevel 2 echo errorlevel too high, bad diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 76665765b26..0f33f2ae05e 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -1704,6 +1704,7 @@ value1 1 ------------ Testing Errorlevel ------------ 9009 +-9999 1 errorlevel just right, good errorlevel with leading zero just right, good diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index 3ed98bcf73e..cd8d7bbdde7 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -251,7 +251,7 @@ typedef struct _FOR_CONTEXT { * variables and batch parameters substitution already done. */ extern WCHAR quals[MAXSTRING], param1[MAXSTRING], param2[MAXSTRING]; -extern DWORD errorlevel; +extern int errorlevel; extern BATCH_CONTEXT *context; extern FOR_CONTEXT forloopcontext; extern BOOL delayedsubst; diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 1a37e93ccc6..30362c4f963 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -36,7 +36,7 @@ extern const WCHAR inbuilt[][10]; extern struct env_stack *pushd_directories;
BATCH_CONTEXT *context = NULL; -DWORD errorlevel; +int errorlevel; WCHAR quals[MAXSTRING], param1[MAXSTRING], param2[MAXSTRING]; BOOL interactive; FOR_CONTEXT forloopcontext; /* The 'for' loop context */ @@ -1297,7 +1297,7 @@ void WCMD_run_program (WCHAR *command, BOOL called) interactive = oldinteractive; return; } else { - + DWORD exit_code; /* thisDir contains the file to be launched, but with what? eg. a.exe will require a.exe to be launched, a.html may be iexplore */ hinst = FindExecutableW (thisDir, NULL, temp); @@ -1331,8 +1331,8 @@ void WCMD_run_program (WCHAR *command, BOOL called) or for console applications */ if (!interactive || (console && !HIWORD(console))) WaitForSingleObject (pe.hProcess, INFINITE); - GetExitCodeProcess (pe.hProcess, &errorlevel); - if (errorlevel == STILL_ACTIVE) errorlevel = 0; + GetExitCodeProcess (pe.hProcess, &exit_code); + errorlevel = (exit_code == STILL_ACTIVE) ? 0 : exit_code;
CloseHandle(pe.hProcess); CloseHandle(pe.hThread);
From: Eric Pouech epouech@codeweavers.com
Introducting CMD_IF_CONDITION to hold IF condition.
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/builtins.c | 205 ++++------------------------------- programs/cmd/wcmd.h | 29 +++++ programs/cmd/wcmdmain.c | 229 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 268 insertions(+), 195 deletions(-)
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index b4c00de1e2b..76f75bb2d96 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -2739,171 +2739,6 @@ void WCMD_popd (void) { LocalFree (temp); }
-/******************************************************************* - * evaluate_if_comparison - * - * Evaluates an "if" comparison operation - * - * PARAMS - * leftOperand [I] left operand, non NULL - * operator [I] "if" binary comparison operator, non NULL - * rightOperand [I] right operand, non NULL - * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive - * - * RETURNS - * Success: 1 if operator applied to the operands evaluates to TRUE - * 0 if operator applied to the operands evaluates to FALSE - * Failure: -1 if operator is not recognized - */ -static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operator, - const WCHAR *rightOperand, int caseInsensitive) -{ - WCHAR *endptr_leftOp, *endptr_rightOp; - long int leftOperand_int, rightOperand_int; - BOOL int_operands; - - /* == is a special case, as it always compares strings */ - if (!lstrcmpiW(operator, L"==")) - return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0 - : lstrcmpW (leftOperand, rightOperand) == 0; - - /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */ - leftOperand_int = wcstol(leftOperand, &endptr_leftOp, 0); - rightOperand_int = wcstol(rightOperand, &endptr_rightOp, 0); - int_operands = (!*endptr_leftOp) && (!*endptr_rightOp); - - /* Perform actual (integer or string) comparison */ - if (!lstrcmpiW(operator, L"lss")) { - if (int_operands) - return leftOperand_int < rightOperand_int; - else - return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) < 0 - : lstrcmpW (leftOperand, rightOperand) < 0; - } - - if (!lstrcmpiW(operator, L"leq")) { - if (int_operands) - return leftOperand_int <= rightOperand_int; - else - return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) <= 0 - : lstrcmpW (leftOperand, rightOperand) <= 0; - } - - if (!lstrcmpiW(operator, L"equ")) { - if (int_operands) - return leftOperand_int == rightOperand_int; - else - return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0 - : lstrcmpW (leftOperand, rightOperand) == 0; - } - - if (!lstrcmpiW(operator, L"neq")) { - if (int_operands) - return leftOperand_int != rightOperand_int; - else - return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) != 0 - : lstrcmpW (leftOperand, rightOperand) != 0; - } - - if (!lstrcmpiW(operator, L"geq")) { - if (int_operands) - return leftOperand_int >= rightOperand_int; - else - return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) >= 0 - : lstrcmpW (leftOperand, rightOperand) >= 0; - } - - if (!lstrcmpiW(operator, L"gtr")) { - if (int_operands) - return leftOperand_int > rightOperand_int; - else - return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) > 0 - : lstrcmpW (leftOperand, rightOperand) > 0; - } - - return -1; -} - -int evaluate_if_condition(WCHAR *p, WCHAR **command, int *test, int *negate) -{ - WCHAR condition[MAX_PATH]; - int caseInsensitive = (wcsstr(quals, L"/I") != NULL); - - *negate = !lstrcmpiW(param1,L"not"); - lstrcpyW(condition, (*negate ? param2 : param1)); - WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition)); - - if (!lstrcmpiW(condition, L"errorlevel")) { - WCHAR *param = WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE); - WCHAR *endptr; - int param_int = wcstol(param, &endptr, 10); - if (endptr == param || *endptr) goto syntax_err; - *test = (errorlevel >= param_int); - WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE); - } - else if (!lstrcmpiW(condition, L"exist")) { - WIN32_FIND_DATAW fd; - HANDLE hff; - WCHAR *param = WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE); - int len = lstrlenW(param); - - if (!len) { - *test = FALSE; - } else { - /* FindFirstFile does not like a directory path ending in '' or '/', so append a '.' */ - if (param[len-1] == '\' || param[len-1] == '/') wcscat(param, L"."); - - hff = FindFirstFileW(param, &fd); - *test = (hff != INVALID_HANDLE_VALUE); - if (*test) FindClose(hff); - } - - WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE); - } - else if (!lstrcmpiW(condition, L"defined")) { - *test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE), - NULL, 0) > 0); - WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE); - } - else { /* comparison operation */ - WCHAR leftOperand[MAXSTRING], rightOperand[MAXSTRING], operator[MAXSTRING]; - WCHAR *paramStart; - - lstrcpyW(leftOperand, WCMD_parameter(p, (*negate)+caseInsensitive, ¶mStart, TRUE, FALSE)); - if (!*leftOperand) - goto syntax_err; - - /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */ - p = paramStart + lstrlenW(leftOperand); - while (*p == ' ' || *p == '\t') - p++; - - if (!wcsncmp(p, L"==", lstrlenW(L"=="))) - lstrcpyW(operator, L"=="); - else { - lstrcpyW(operator, WCMD_parameter(p, 0, ¶mStart, FALSE, FALSE)); - if (!*operator) goto syntax_err; - } - p += lstrlenW(operator); - - lstrcpyW(rightOperand, WCMD_parameter(p, 0, ¶mStart, TRUE, FALSE)); - if (!*rightOperand) - goto syntax_err; - - *test = evaluate_if_comparison(leftOperand, operator, rightOperand, caseInsensitive); - if (*test == -1) - goto syntax_err; - - p = paramStart + lstrlenW(rightOperand); - WCMD_parameter(p, 0, command, FALSE, FALSE); - } - - return 1; - -syntax_err: - return -1; -} - /**************************************************************************** * WCMD_if * @@ -2920,26 +2755,28 @@ syntax_err: */ void WCMD_if (WCHAR *p, CMD_NODE **cmdList) { - int negate; /* Negate condition */ - int test; /* Condition evaluation result */ - WCHAR *command; - - /* Function evaluate_if_condition relies on the global variables quals, param1 and param2 - set in a call to WCMD_parse before */ - if (evaluate_if_condition(p, &command, &test, &negate) == -1) - goto syntax_err; - - WINE_TRACE("p: %s, quals: %s, param1: %s, param2: %s, command: %s\n", - wine_dbgstr_w(p), wine_dbgstr_w(quals), wine_dbgstr_w(param1), - wine_dbgstr_w(param2), wine_dbgstr_w(command)); - - /* Process rest of IF statement which is on the same line - Note: This may process all or some of the cmdList (eg a GOTO) */ - WCMD_part_execute(cmdList, command, TRUE, (test != negate)); - return; + CMD_IF_CONDITION if_cond; + WCHAR *command; + int test; + + if (if_condition_create(p, &command, &if_cond)) + { + TRACE("%s\n", debugstr_if_condition(&if_cond)); + if (if_condition_evaluate(&if_cond, &test)) + { + WINE_TRACE("p: %s, quals: %s, param1: %s, param2: %s, command: %s\n", + wine_dbgstr_w(p), wine_dbgstr_w(quals), wine_dbgstr_w(param1), + wine_dbgstr_w(param2), wine_dbgstr_w(command));
-syntax_err: - WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR)); + /* Process rest of IF statement which is on the same line + Note: This may process all or some of the cmdList (eg a GOTO) */ + WCMD_part_execute(cmdList, command, TRUE, test); + } + if_condition_dispose(&if_cond); + return; + } + + WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR)); }
/**************************************************************************** diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index cd8d7bbdde7..def31d7e2d3 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -60,6 +60,29 @@ typedef enum _CMD_OPERATOR
/* Data structure to hold commands to be processed */
+enum cond_operator {CMD_IF_ERRORLEVEL, CMD_IF_EXIST, CMD_IF_DEFINED, + CMD_IF_BINOP_EQUAL /* == */, CMD_IF_BINOP_LSS, CMD_IF_BINOP_LEQ, CMD_IF_BINOP_EQU, + CMD_IF_BINOP_NEQ, CMD_IF_BINOP_GEQ, CMD_IF_BINOP_GTR}; +typedef struct _CMD_IF_CONDITION +{ + unsigned case_insensitive : 1, + negated : 1, + op; + union + { + /* CMD_IF_ERRORLEVEL */ + int level; + /* CMD_IF_EXIST, CMD_IF_DEFINED */ + const WCHAR *operand; + /* CMD_BINOP_EQUAL, CMD_BINOP_LSS, CMD_BINOP_LEQ, CMD_BINOP_EQU, CMD_BINOP_NEQ, CMD_BINOP_GEQ, CMD_BINOP_GTR */ + struct + { + const WCHAR *left; + const WCHAR *right; + }; + }; +} CMD_IF_CONDITION; + typedef struct _CMD_COMMAND { WCHAR *command; /* Command string to execute */ @@ -100,6 +123,12 @@ static inline int CMD_node_get_depth(const CMD_NODE *node) } /* end temporary */
+/* temporary helpers for parsing transition */ +BOOL if_condition_create(WCHAR *start, WCHAR **end, CMD_IF_CONDITION *cond); +void if_condition_dispose(CMD_IF_CONDITION *); +BOOL if_condition_evaluate(CMD_IF_CONDITION *cond, int *test); +const char *debugstr_if_condition(const CMD_IF_CONDITION *cond); + void WCMD_assoc (const WCHAR *, BOOL); void WCMD_batch (WCHAR *, WCHAR *, BOOL, WCHAR *, HANDLE); void WCMD_call (WCHAR *command); diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 30362c4f963..5f1a94b39b3 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -1035,6 +1035,151 @@ static CMD_NODE *node_create_binary(CMD_OPERATOR op, CMD_NODE *l, CMD_NODE *r) return new; }
+void if_condition_dispose(CMD_IF_CONDITION *cond) +{ + switch (cond->op) + { + case CMD_IF_ERRORLEVEL: + break; + case CMD_IF_EXIST: + case CMD_IF_DEFINED: + free((void*)cond->operand); + break; + case CMD_IF_BINOP_EQUAL: + case CMD_IF_BINOP_LSS: + case CMD_IF_BINOP_LEQ: + case CMD_IF_BINOP_EQU: + case CMD_IF_BINOP_NEQ: + case CMD_IF_BINOP_GEQ: + case CMD_IF_BINOP_GTR: + free((void*)cond->left); + free((void*)cond->right); + break; + } +} + +BOOL if_condition_create(WCHAR *start, WCHAR **end, CMD_IF_CONDITION *cond) +{ + WCHAR *param_start; + const WCHAR *param_copy; + int narg = 0; + + if (cond) memset(cond, 0, sizeof(*cond)); + param_copy = WCMD_parameter(start, narg++, ¶m_start, TRUE, FALSE); + /* /I is the only option supported */ + if (!wcsicmp(param_copy, L"/I")) + { + param_copy = WCMD_parameter(start, narg++, ¶m_start, TRUE, FALSE); + if (cond) cond->case_insensitive = 1; + } + if (!wcsicmp(param_copy, L"NOT")) + { + param_copy = WCMD_parameter(start, narg++, ¶m_start, TRUE, FALSE); + if (cond) cond->negated = 1; + } + if (!wcsicmp(param_copy, L"errorlevel")) + { + WCHAR *endptr; + int level; + param_copy = WCMD_parameter(start, narg++, ¶m_start, TRUE, FALSE); + if (cond) cond->op = CMD_IF_ERRORLEVEL; + level = wcstol(param_copy, &endptr, 10); + if (*endptr) return FALSE; + if (cond) cond->level = level; + } + else if (!wcsicmp(param_copy, L"exist")) + { + param_copy = WCMD_parameter(start, narg++, ¶m_start, FALSE, FALSE); + if (cond) cond->op = CMD_IF_EXIST; + if (cond) cond->operand = wcsdup(param_copy); + } + else if (!wcsicmp(param_copy, L"defined")) + { + param_copy = WCMD_parameter(start, narg++, ¶m_start, TRUE, FALSE); + if (cond) cond->op = CMD_IF_DEFINED; + if (cond) cond->operand = wcsdup(param_copy); + } + else /* comparison operation */ + { + if (*param_copy == L'\0') return FALSE; + param_copy = WCMD_parameter(start, narg - 1, ¶m_start, TRUE, FALSE); + if (cond) cond->left = wcsdup(param_copy); + + start = WCMD_skip_leading_spaces(param_start + wcslen(param_copy)); + + /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */ + if (start[0] == L'=' && start[1] == L'=') + { + start += 2; /* == */ + if (cond) cond->op = CMD_IF_BINOP_EQUAL; + } + else + { + static struct + { + const WCHAR *name; + enum cond_operator binop; + } + allowed_operators[] = {{L"lss", CMD_IF_BINOP_LSS}, + {L"leq", CMD_IF_BINOP_LEQ}, + {L"equ", CMD_IF_BINOP_EQU}, + {L"neq", CMD_IF_BINOP_NEQ}, + {L"geq", CMD_IF_BINOP_GEQ}, + {L"gtr", CMD_IF_BINOP_GTR}, + }; + int i; + + param_copy = WCMD_parameter(start, 0, ¶m_start, FALSE, FALSE); + for (i = 0; i < ARRAY_SIZE(allowed_operators); i++) + if (!wcsicmp(param_copy, allowed_operators[i].name)) break; + if (i == ARRAY_SIZE(allowed_operators)) + { + if (cond) free((void*)cond->left); + return FALSE; + } + if (cond) cond->op = allowed_operators[i].binop; + start += wcslen(param_copy); + } + + param_copy = WCMD_parameter(start, 0, ¶m_start, TRUE, FALSE); + if (*param_copy == L'\0') + { + if (cond) free((void*)cond->left); + return FALSE; + } + if (cond) cond->right = wcsdup(param_copy); + + start = param_start + wcslen(param_copy); + narg = 0; + } + /* check all remaning args are present, and compute pointer to end of condition */ + param_copy = WCMD_parameter(start, narg, end, TRUE, FALSE); + return cond || *param_copy != L'\0'; +} + +const char *debugstr_if_condition(const CMD_IF_CONDITION *cond) +{ + const char *header = wine_dbg_sprintf("{{%s%s", cond->negated ? "not " : "", cond->case_insensitive ? "nocase " : ""); + + switch (cond->op) + { + case CMD_IF_ERRORLEVEL: return wine_dbg_sprintf("%serrorlevel %d}}", header, cond->level); + case CMD_IF_EXIST: return wine_dbg_sprintf("%sexist %ls}}", header, cond->operand); + case CMD_IF_DEFINED: return wine_dbg_sprintf("%sdefined %ls}}", header, cond->operand); + case CMD_IF_BINOP_EQUAL: return wine_dbg_sprintf("%s%ls == %ls}}", header, cond->left, cond->right); + + case CMD_IF_BINOP_LSS: return wine_dbg_sprintf("%s%ls LSS %ls}}", header, cond->left, cond->right); + case CMD_IF_BINOP_LEQ: return wine_dbg_sprintf("%s%ls LEQ %ls}}", header, cond->left, cond->right); + case CMD_IF_BINOP_EQU: return wine_dbg_sprintf("%s%ls EQU %ls}}", header, cond->left, cond->right); + case CMD_IF_BINOP_NEQ: return wine_dbg_sprintf("%s%ls NEQ %ls}}", header, cond->left, cond->right); + case CMD_IF_BINOP_GEQ: return wine_dbg_sprintf("%s%ls GEQ %ls}}", header, cond->left, cond->right); + case CMD_IF_BINOP_GTR: return wine_dbg_sprintf("%s%ls GTR %ls}}", header, cond->left, cond->right); + default: + FIXME("Unexpected condition operator %u\n", cond->op); + return "{{}}"; + } +} + static void init_msvcrt_io_block(STARTUPINFOW* st) { STARTUPINFOW st_p; @@ -2092,20 +2237,12 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_NODE **output, HANDLE To be able to handle ('s in the condition part take as much as evaluate_if_condition would take and skip parsing it here. */ } else if (WCMD_keyword_ws_found(L"if", curPos)) { - int negate; /* Negate condition */ - int test; /* Condition evaluation result */ WCHAR *p, *command;
inIf = TRUE;
- p = curPos+(lstrlenW(L"if")); - while (*p == ' ' || *p == '\t') - p++; - WCMD_parse (p, quals, param1, param2); - - /* Function evaluate_if_condition relies on the global variables quals, param1 and param2 - set in a call to WCMD_parse before */ - if (evaluate_if_condition(p, &command, &test, &negate) != -1) + p = WCMD_skip_leading_spaces(curPos + 2); /* "if" */ + if (if_condition_create(p, &command, NULL)) { int if_condition_len = command - curPos; WINE_TRACE("p: %s, quals: %s, param1: %s, param2: %s, command: %s, if_condition_len: %d\n", @@ -2115,7 +2252,6 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_NODE **output, HANDLE (*curLen)+=if_condition_len; curPos+=if_condition_len; } - if (WCMD_keyword_ws_found(L"set", curPos)) ignoreBracket = TRUE;
@@ -2503,6 +2639,77 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_NODE **output, HANDLE return extraSpace; }
+BOOL if_condition_evaluate(CMD_IF_CONDITION *cond, int *test) +{ + int (WINAPI *cmp)(const WCHAR*, const WCHAR*) = cond->case_insensitive ? lstrcmpiW : lstrcmpW; + + TRACE("About to evaluate condition %s\n", debugstr_if_condition(cond)); + *test = 0; + switch (cond->op) + { + case CMD_IF_ERRORLEVEL: + *test = errorlevel >= cond->level; + break; + case CMD_IF_EXIST: + { + WIN32_FIND_DATAW fd; + HANDLE hff; + size_t len = wcslen(cond->operand); + + if (len) + { + /* FindFirstFile does not like a directory path ending in '' or '/', so append a '.' */ + if (cond->operand[len - 1] == '\' || cond->operand[len - 1] == '/') + { + WCHAR *new = xrealloc((void*)cond->operand, (wcslen(cond->operand) + 2) * sizeof(WCHAR)); + wcscat(new, L"."); + cond->operand = new; + } + hff = FindFirstFileW(cond->operand, &fd); + *test = (hff != INVALID_HANDLE_VALUE); + if (*test) FindClose(hff); + } + } + break; + case CMD_IF_DEFINED: + *test = GetEnvironmentVariableW(cond->operand, NULL, 0) > 0; + break; + case CMD_IF_BINOP_EQUAL: + /* == is a special case, as it always compares strings */ + *test = (*cmp)(cond->left, cond->right) == 0; + break; + default: + { + int left_int, right_int; + WCHAR *end_left, *end_right; + int cmp_val; + + /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */ + left_int = wcstol(cond->left, &end_left, 0); + right_int = wcstol(cond->right, &end_right, 0); + if (end_left > cond->left && !*end_left && end_right > cond->right && !*end_right) + cmp_val = left_int - right_int; + else + cmp_val = (*cmp)(cond->left, cond->right); + switch (cond->op) + { + case CMD_IF_BINOP_LSS: *test = cmp_val < 0; break; + case CMD_IF_BINOP_LEQ: *test = cmp_val <= 0; break; + case CMD_IF_BINOP_EQU: *test = cmp_val == 0; break; + case CMD_IF_BINOP_NEQ: *test = cmp_val != 0; break; + case CMD_IF_BINOP_GEQ: *test = cmp_val >= 0; break; + case CMD_IF_BINOP_GTR: *test = cmp_val > 0; break; + default: + FIXME("Unexpected comparison operator %u\n", cond->op); + return FALSE; + } + } + break; + } + if (cond->negated) *test ^= 1; + return TRUE; +} + /*************************************************************************** * WCMD_process_commands *
maybe I got it right in V5