diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c index 8faa2970870..1e60848b2a5 100644 --- a/programs/cmd/batch.c +++ b/programs/cmd/batch.c @@ -19,6 +19,9 @@ */ #include "wcmd.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(cmd); extern int echo_mode; extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH]; @@ -301,7 +304,7 @@ void WCMD_splitpath(const WCHAR* path, WCHAR* drv, WCHAR* dir, WCHAR* name, WCHA * Hence search forwards until find an invalid modifier, and then * backwards until find for variable or 0-9 */ -void WCMD_HandleTildaModifiers(WCHAR **start, WCHAR *forVariable) { +void WCMD_HandleTildaModifiers(WCHAR **start, WCHAR *forVariable, WCHAR *forValue, BOOL justFors) { #define NUMMODIFIERS 11 static const WCHAR validmodifiers[NUMMODIFIERS] = { @@ -324,10 +327,10 @@ void WCMD_HandleTildaModifiers(WCHAR **start, WCHAR *forVariable) { BOOL skipFileParsing = FALSE; BOOL doneModifier = FALSE; - /* Search forwards until find invalid WCHARacter modifier */ + /* Search forwards until find invalid character modifier */ while (!finished) { - /* Work on the previous WCHARacter */ + /* Work on the previous character */ if (lastModifier != NULL) { for (i=0; i= '0' || *lastModifier <= '9') && - (forVariable != NULL) && - (toupperW(*lastModifier) != toupperW(*forVariable))) { + while (lastModifier > firstModifier) { + WINE_TRACE("Looking backwards for parameter id: %s / %s\n", + wine_dbgstr_w(lastModifier), wine_dbgstr_w(forVariable)); - /* Its not... Step backwards until it matches or we get to the start */ - while (toupperW(*lastModifier) != toupperW(*forVariable) && - lastModifier > firstModifier) { + if (!justFors && context && (*lastModifier >= '0' || *lastModifier <= '9')) { + /* Its a valid parameter identifier - OK */ + break; + + } else if (forVariable && *lastModifier == *(forVariable+1)) { + /* Its a valid parameter identifier - OK */ + break; + + } else { lastModifier--; } - if (lastModifier == firstModifier) return; /* Invalid syntax */ } + if (lastModifier == firstModifier) return; /* Invalid syntax */ /* Extract the parameter to play with */ if ((*lastModifier >= '0' && *lastModifier <= '9')) { strcpyW(outputparam, WCMD_parameter (context -> command, *lastModifier-'0' + context -> shift_count[*lastModifier-'0'], NULL)); } else { - /* FIXME: Retrieve 'for' variable %c\n", *lastModifier); */ - /* Need to get 'for' loop variable into outputparam */ - return; + strcpyW(outputparam, forValue); } /* So now, firstModifier points to beginning of modifiers, lastModifier diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index 027f2bdda8f..8fbf292a5a2 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -992,40 +992,6 @@ void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable, return; } -/***************************************************************************** - * WCMD_Execute - * - * Execute a command after substituting variable text for the supplied parameter - */ - -void WCMD_execute (WCHAR *orig_cmd, WCHAR *param, WCHAR *subst, CMD_LIST **cmdList) { - - WCHAR *new_cmd, *p, *s, *dup; - int size; - - if (param) { - size = (strlenW (orig_cmd) + 1) * sizeof(WCHAR); - new_cmd = (WCHAR *) LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, size); - dup = s = WCMD_strdupW(orig_cmd); - - while ((p = strstrW (s, param))) { - *p = '\0'; - size += strlenW (subst) * sizeof(WCHAR); - new_cmd = (WCHAR *) LocalReAlloc ((HANDLE)new_cmd, size, 0); - strcatW (new_cmd, s); - strcatW (new_cmd, subst); - s = p + strlenW (param); - } - strcatW (new_cmd, s); - WCMD_process_command (new_cmd, cmdList); - free (dup); - LocalFree ((HANDLE)new_cmd); - } else { - WCMD_process_command (orig_cmd, cmdList); - } -} - - /************************************************************************** * WCMD_give_help * @@ -1067,7 +1033,7 @@ void WCMD_goto (CMD_LIST **cmdList) { WCHAR string[MAX_PATH]; /* Do not process any more parts of a processed multipart or multilines command */ - *cmdList = NULL; + if (cmdList) *cmdList = NULL; if (param1[0] == 0x00) { WCMD_output (WCMD_LoadMessage(WCMD_NOARG)); diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index 0781fb6c8f4..1d822898c03 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -65,7 +65,6 @@ void WCMD_pause (void); void WCMD_pipe (CMD_LIST **command, WCHAR *var, WCHAR *val); void WCMD_popd (void); void WCMD_print_error (void); -void WCMD_process_command (WCHAR *command, CMD_LIST **cmdList); void WCMD_pushd (WCHAR *); int WCMD_read_console (WCHAR *string, int str_len); void WCMD_remove_dir (WCHAR *command); @@ -92,7 +91,7 @@ WCHAR *WCMD_parameter (WCHAR *s, int n, WCHAR **where); WCHAR *WCMD_strtrim_leading_spaces (WCHAR *string); void WCMD_strtrim_trailing_spaces (WCHAR *string); void WCMD_opt_s_strip_quotes(WCHAR *cmd); -void WCMD_HandleTildaModifiers(WCHAR **start, WCHAR *forVariable); +void WCMD_HandleTildaModifiers(WCHAR **start, WCHAR *forVariable, WCHAR *forValue, BOOL justFors); BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll); void WCMD_splitpath(const WCHAR* path, WCHAR* drv, WCHAR* dir, WCHAR* name, WCHAR* ext); diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 83185908e08..b44b0ed7967 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -95,7 +95,7 @@ static char *output_bufA = NULL; #define MAX_WRITECONSOLE_SIZE 65535 BOOL unicodePipes = FALSE; -static WCHAR *WCMD_expand_envvar(WCHAR *start); +static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR *forvar, WCHAR *forVal); /***************************************************************************** * Main entry point. This is a console application so we have a main() not a @@ -456,6 +456,87 @@ int wmain (int argc, WCHAR *argvW[]) return 0; } +/***************************************************************************** + * Expand the command. Native expands lines from batch programs as they are + * read in and not again, except for 'for' variable substitution. + * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%" + */ +void handleExpansion(WCHAR *cmd, BOOL justFors, WCHAR *forVariable, WCHAR *forValue) { + + /* For commands in a context (batch program): */ + /* Expand environment variables in a batch file %{0-9} first */ + /* including support for any ~ modifiers */ + /* Additionally: */ + /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */ + /* names allowing environment variable overrides */ + /* NOTE: To support the %PATH:xxx% syntax, also perform */ + /* manual expansion of environment variables here */ + + WCHAR *p = cmd; + WCHAR *s, *t; + int i; + + while ((p = strchrW(p, '%'))) { + + WINE_TRACE("Translate command:%s %d (at: %s)\n", + wine_dbgstr_w(cmd), justFors, wine_dbgstr_w(p)); + i = *(p+1) - '0'; + + /* Don't touch %% unless its in Batch */ + if (!justFors && *(p+1) == '%') { + if (context) { + s = WCMD_strdupW(p+1); + strcpyW (p, s); + free (s); + } + p+=1; + + /* Replace %~ modifications if in batch program */ + } else if (*(p+1) == '~') { + WCMD_HandleTildaModifiers(&p, forVariable, forValue, justFors); + p++; + + /* Replace use of %0...%9 if in batch program*/ + } else if (!justFors && context && (i >= 0) && (i <= 9)) { + s = WCMD_strdupW(p+2); + t = WCMD_parameter (context -> command, i + context -> shift_count[i], NULL); + strcpyW (p, t); + strcatW (p, s); + free (s); + + /* Replace use of %* if in batch program*/ + } else if (!justFors && context && *(p+1)=='*') { + WCHAR *startOfParms = NULL; + s = WCMD_strdupW(p+2); + t = WCMD_parameter (context -> command, 1, &startOfParms); + if (startOfParms != NULL) strcpyW (p, startOfParms); + else *p = 0x00; + strcatW (p, s); + free (s); + + } else if (forVariable && + (CompareString (LOCALE_USER_DEFAULT, + SORT_STRINGSORT, + p, + strlenW(forVariable), + forVariable, -1) == 2)) { + s = WCMD_strdupW(p + strlenW(forVariable)); + strcpyW(p, forValue); + strcatW(p, s); + free(s); + + } else if (!justFors) { + p = WCMD_expand_envvar(p, forVariable, forValue); + + /* In a FOR loop, see if this is the variable to replace */ + } else { /* Ignore %'s on second pass of batch program */ + p++; + } + } + + return; +} + /***************************************************************************** * Process one command. If the command is EXIT this routine does not return. @@ -463,9 +544,11 @@ int wmain (int argc, WCHAR *argvW[]) */ -void WCMD_process_command (WCHAR *command, CMD_LIST **cmdList) +void WCMD_execute (WCHAR *command, + WCHAR *forVariable, WCHAR *forValue, + CMD_LIST **cmdList) { - WCHAR *cmd, *p, *s, *t, *redir; + WCHAR *cmd, *p, *redir; int status, i; DWORD count, creationDisposition; HANDLE h; @@ -480,81 +563,24 @@ void WCMD_process_command (WCHAR *command, CMD_LIST **cmdList) STD_OUTPUT_HANDLE, STD_ERROR_HANDLE}; + WINE_TRACE("command on entry:%s (%p), with '%s'='%s'\n", + wine_dbgstr_w(command), cmdList, + wine_dbgstr_w(forVariable), wine_dbgstr_w(forValue)); + /* Move copy of the command onto the heap so it can be expanded */ new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR)); strcpyW(new_cmd, command); - /* For commands in a context (batch program): */ - /* Expand environment variables in a batch file %{0-9} first */ - /* including support for any ~ modifiers */ - /* Additionally: */ - /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */ - /* names allowing environment variable overrides */ - /* NOTE: To support the %PATH:xxx% syntax, also perform */ - /* manual expansion of environment variables here */ - - p = new_cmd; - while ((p = strchrW(p, '%'))) { - i = *(p+1) - '0'; - - /* Don't touch %% */ - if (*(p+1) == '%') { - p+=2; - - /* Replace %~ modifications if in batch program */ - } else if (context && *(p+1) == '~') { - WCMD_HandleTildaModifiers(&p, NULL); - p++; - - /* Replace use of %0...%9 if in batch program*/ - } else if (context && (i >= 0) && (i <= 9)) { - s = WCMD_strdupW(p+2); - t = WCMD_parameter (context -> command, i + context -> shift_count[i], NULL); - strcpyW (p, t); - strcatW (p, s); - free (s); - - /* Replace use of %* if in batch program*/ - } else if (context && *(p+1)=='*') { - WCHAR *startOfParms = NULL; - s = WCMD_strdupW(p+2); - t = WCMD_parameter (context -> command, 1, &startOfParms); - if (startOfParms != NULL) strcpyW (p, startOfParms); - else *p = 0x00; - strcatW (p, s); - free (s); - - } else { - p = WCMD_expand_envvar(p); - } - } + /* 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), forVariable, forValue); cmd = new_cmd; - /* In a batch program, unknown variables are replace by nothing */ - /* so remove any remaining %var% */ - if (context) { - p = cmd; - while ((p = strchrW(p, '%'))) { - if (*(p+1) == '%') { - p+=2; - } else { - s = strchrW(p+1, '%'); - if (!s) { - *p=0x00; - } else { - t = WCMD_strdupW(s+1); - strcpyW(p, t); - free(t); - } - } - } - - /* Show prompt before batch line IF echo is on and in batch program */ - if (echo_mode && (cmd[0] != '@')) { - WCMD_show_prompt(); - WCMD_output_asis ( cmd); - WCMD_output_asis ( newline); - } + /* Show prompt before batch line IF echo is on and in batch program */ + if (context && echo_mode && (cmd[0] != '@')) { + WCMD_show_prompt(); + WCMD_output_asis ( cmd); + WCMD_output_asis ( newline); } /* @@ -1533,7 +1559,7 @@ void WCMD_pipe (CMD_LIST **cmdEntry, WCHAR *var, WCHAR *val) { * * Expands environment variables, allowing for WCHARacter substitution */ -static WCHAR *WCMD_expand_envvar(WCHAR *start) { +static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR *forVar, WCHAR *forVal) { WCHAR *endOfVar = NULL, *s; WCHAR *colonpos = NULL; WCHAR thisVar[MAXSTRING]; @@ -1551,20 +1577,38 @@ static WCHAR *WCMD_expand_envvar(WCHAR *start) { static const WCHAR CdP[] = {'%','C','D','%','\0'}; static const WCHAR Random[] = {'R','A','N','D','O','M','\0'}; static const WCHAR RandomP[] = {'%','R','A','N','D','O','M','%','\0'}; + static const WCHAR Delims[] = {'%',' ',':','\0'}; + + WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start), + wine_dbgstr_w(forVal), wine_dbgstr_w(forVar)); /* Find the end of the environment variable, and extract name */ - endOfVar = strchrW(start+1, '%'); - if (endOfVar == NULL) { + endOfVar = strpbrkW(start+1, Delims); + + if (endOfVar == NULL || *endOfVar==' ') { + /* In batch program, missing terminator for % and no following ':' just removes the '%' */ - s = WCMD_strdupW(start + 1); - strcpyW (start, s); - free(s); + if (context) { + s = WCMD_strdupW(start + 1); + strcpyW (start, s); + free(s); + return start; + } else { - /* FIXME: Some other special conditions here depending on whether - in batch, complex or not, and whether env var exists or not! */ - return start; + /* In command processing, just ignore it - allows command line + syntax like: for %i in (a.a) do echo %i */ + return start+1; + } } + + /* If ':' found, process remaining up until '%' (or stop at ':' if + a missing '%' */ + if (*endOfVar==':') { + WCHAR *endOfVar2 = strchrW(endOfVar+1, '%'); + if (endOfVar2 != NULL) endOfVar = endOfVar2; + } + memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR)); thisVar[(endOfVar - start)+1] = 0x00; colonpos = strchrW(thisVar+1, ':'); @@ -1627,6 +1671,16 @@ static WCHAR *WCMD_expand_envvar(WCHAR *start) { wsprintf(thisVarContents, fmt, rand() % 32768); len = strlenW(thisVarContents); + /* Look for a matching 'for' variable */ + } else if (forVar && + (CompareString (LOCALE_USER_DEFAULT, + SORT_STRINGSORT, + thisVar, + (colonpos - thisVar) - 1, + forVar, -1) == 2)) { + strcpyW(thisVarContents, forVal); + len = strlenW(thisVarContents); + } else { len = ExpandEnvironmentStrings(thisVar, thisVarContents, @@ -1952,6 +2006,9 @@ WCHAR *WCMD_ReadAndParseLine(WCHAR *optionalcmd, CMD_LIST **output, HANDLE readF WCMD_output_asis(newline); } + /* Replace env vars if in a batch context */ + if (context) handleExpansion(extraSpace, FALSE, NULL, NULL); + /* Start with an empty string */ curLen = 0; @@ -2234,6 +2291,7 @@ WCHAR *WCMD_ReadAndParseLine(WCHAR *optionalcmd, CMD_LIST **output, HANDLE readF if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) break; } curPos = extraSpace; + if (context) handleExpansion(extraSpace, FALSE, NULL, NULL); } }