https://git.reactos.org/?p=reactos.git;a=commitdiff;h=cb2a9c31a6c8c207f22852...
commit cb2a9c31a6c8c207f22852c68fc1464dd3b52080 Author: Hermès Bélusca-Maïto hermes.belusca-maito@reactos.org AuthorDate: Sat Jul 4 17:40:58 2020 +0200 Commit: Hermès Bélusca-Maïto hermes.belusca-maito@reactos.org CommitDate: Sat Sep 19 19:44:54 2020 +0200
[CMD] Some fixes for getting the enhanced '%~XXX' batch/FOR variables. CORE-11857 CORE-13736
It will be followed with a separate fix for the FOR-loop code. Fixes some cmd_winetests.
A NULL pointer can be returned for a valid existing batch/FOR variable, in which case the enhanced-variable getter should return an empty string. This situation can happen e.g. when forcing a FOR-loop to tokenize a text line with not enough tokens in it. --- base/shell/cmd/batch.c | 26 +++++--- base/shell/cmd/batch.h | 7 ++- base/shell/cmd/cmd.c | 164 +++++++++++++++++++++++++++++++++++-------------- base/shell/cmd/cmd.h | 7 ++- 4 files changed, 146 insertions(+), 58 deletions(-)
diff --git a/base/shell/cmd/batch.c b/base/shell/cmd/batch.c index a7dfa2fd11a..e9a1e5aa76e 100644 --- a/base/shell/cmd/batch.c +++ b/base/shell/cmd/batch.c @@ -74,29 +74,35 @@ TCHAR textline[BATCH_BUFFSIZE]; /* * Returns a pointer to the n'th parameter of the current batch file. * If no such parameter exists returns pointer to empty string. - * If no batch file is current, returns NULL - * + * If no batch file is current, returns NULL. */ -LPTSTR FindArg(TCHAR Char, BOOL *IsParam0) +BOOL +FindArg( + IN TCHAR Char, + OUT PCTSTR* ArgPtr, + OUT BOOL* IsParam0) { - LPTSTR pp; + PCTSTR pp; INT n = Char - _T('0');
- TRACE ("FindArg: (%d)\n", n); + TRACE("FindArg: (%d)\n", n); + + *ArgPtr = NULL;
if (n < 0 || n > 9) - return NULL; + return FALSE;
n = bc->shiftlevel[n]; *IsParam0 = (n == 0); pp = bc->params;
- /* Step up the strings till we reach the end */ - /* or the one we want */ + /* Step up the strings till we reach + * the end or the one we want. */ while (*pp && n--) - pp += _tcslen (pp) + 1; + pp += _tcslen(pp) + 1;
- return pp; + *ArgPtr = pp; + return TRUE; }
diff --git a/base/shell/cmd/batch.h b/base/shell/cmd/batch.h index de425ee2f11..d40b640e421 100644 --- a/base/shell/cmd/batch.h +++ b/base/shell/cmd/batch.h @@ -44,7 +44,12 @@ extern BOOL bEcho; /* The echo flag */ extern TCHAR textline[BATCH_BUFFSIZE]; /* Buffer for reading Batch file lines */
-LPTSTR FindArg(TCHAR, BOOL *); +BOOL +FindArg( + IN TCHAR Char, + OUT PCTSTR* ArgPtr, + OUT BOOL* IsParam0); + VOID ExitBatch(VOID); VOID ExitAllBatches(VOID); INT Batch(LPTSTR, LPTSTR, LPTSTR, PARSED_COMMAND *); diff --git a/base/shell/cmd/cmd.c b/base/shell/cmd/cmd.c index caa60a466fe..c4d41d0f247 100644 --- a/base/shell/cmd/cmd.c +++ b/base/shell/cmd/cmd.c @@ -964,8 +964,10 @@ GetEnvVarOrSpecial(LPCTSTR varName) }
/* Handle the %~var syntax */ -static LPTSTR -GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, BOOL *)) +static PCTSTR +GetEnhancedVar( + IN OUT PCTSTR* pFormat, + IN BOOL (*GetVar)(TCHAR, PCTSTR*, BOOL*)) { static const TCHAR ModifierTable[] = _T("dpnxfsatz"); enum { @@ -980,28 +982,68 @@ GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, BOOL *)) M_SIZE = 256, /* Z: file size */ } Modifiers = 0;
- TCHAR *Format, *FormatEnd; - TCHAR *PathVarName = NULL; - LPTSTR Variable; - TCHAR *VarEnd; + PCTSTR Format, FormatEnd; + PCTSTR PathVarName = NULL; + PCTSTR Variable; + PCTSTR VarEnd; BOOL VariableIsParam0; TCHAR FullPath[MAX_PATH]; - TCHAR FixedPath[MAX_PATH], *Filename, *Extension; + TCHAR FixedPath[MAX_PATH]; + PTCHAR Filename, Extension; HANDLE hFind; WIN32_FIND_DATA w32fd; - TCHAR *In, *Out; + PTCHAR In, Out;
static TCHAR Result[CMDLINE_LENGTH];
- /* There is ambiguity between modifier characters and FOR variables; - * the rule that cmd uses is to pick the longest possible match. - * For example, if there is a %n variable, then out of %~anxnd, - * %~anxn will be substituted rather than just %~an. */ + /* Check whether the current character is a recognized variable. + * If it is not, then restore the previous one: there is indeed + * ambiguity between modifier characters and FOR variables; + * the rule that CMD uses is to pick the longest possible match. + * This case can happen if we have a FOR-variable specification + * of the following form: + * + * %~<modifiers><actual FOR variable character><other characters> + * + * where the FOR variable character is also a similar to a modifier, + * but should not be interpreted as is, and the following other + * characters are not part of the possible modifier characters, and + * are unrelated to the FOR variable (they can be part of a command). + * For example, if there is a %n variable, then out of %~anxnd, + * %~anxn will be substituted rather than just %~an. + * + * In the following examples, all characters 'd','p','n','x' are valid modifiers. + * + * 1. In this example, the FOR variable character is 'x' and the actual + * modifiers are 'dpn'. Parsing will first determine that 'dpnx' + * are modifiers, with the possible (last) valid variable being 'x', + * and will stop at the letter 'g'. Since 'g' is not a valid + * variable, then the actual variable is the lattest one 'x', + * and the modifiers are then actually 'dpn'. + * The FOR-loop will then display the %x variable formatted with 'dpn' + * and will append any other characters following, 'g'. + * + * C:\Temp>for %x in (foo.exe bar.txt) do @echo %~dpnxg + * C:\Temp\foog + * C:\Temp\barg + * + * + * 2. In this second example, the FOR variable character is 'g' and + * the actual modifiers are 'dpnx'. Parsing will determine also that + * the possible (last) valid variable could be 'x', but since it's + * not present in the FOR-variables list, it won't be the case. + * This means that the actual FOR variable character must follow, + * in this case, 'g'. + * + * C:\Temp>for %g in (foo.exe bar.txt) do @echo %~dpnxg + * C:\Temp\foo.exe + * C:\Temp\bar.txt + */
/* First, go through as many modifier characters as possible */ FormatEnd = Format = *pFormat; while (*FormatEnd && _tcschr(ModifierTable, _totlower(*FormatEnd))) - FormatEnd++; + ++FormatEnd;
if (*FormatEnd == _T('$')) { @@ -1012,48 +1054,52 @@ GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, BOOL *)) return NULL;
/* Must be immediately followed by the variable */ - Variable = GetVar(*++FormatEnd, &VariableIsParam0); - if (!Variable) + if (!GetVar(*++FormatEnd, &Variable, &VariableIsParam0)) return NULL; } else { /* Backtrack if necessary to get a variable name match */ - while (!(Variable = GetVar(*FormatEnd, &VariableIsParam0))) + while (!GetVar(*FormatEnd, &Variable, &VariableIsParam0)) { if (FormatEnd == Format) return NULL; - FormatEnd--; + --FormatEnd; } }
- for (; Format < FormatEnd && *Format != _T('$'); Format++) - Modifiers |= 1 << (_tcschr(ModifierTable, _totlower(*Format)) - ModifierTable); - *pFormat = FormatEnd + 1;
+ /* If the variable is empty, return an empty string */ + if (!Variable || !*Variable) + return _T(""); + /* Exclude the leading and trailing quotes */ VarEnd = &Variable[_tcslen(Variable)]; if (*Variable == _T('"')) { - Variable++; + ++Variable; if (VarEnd > Variable && VarEnd[-1] == _T('"')) - VarEnd--; + --VarEnd; }
- if ((char *)VarEnd - (char *)Variable >= sizeof Result) + if ((ULONG_PTR)VarEnd - (ULONG_PTR)Variable >= sizeof(Result)) return _T(""); - memcpy(Result, Variable, (char *)VarEnd - (char *)Variable); + memcpy(Result, Variable, (ULONG_PTR)VarEnd - (ULONG_PTR)Variable); Result[VarEnd - Variable] = _T('\0');
+ /* Now determine the actual modifiers */ + for (; Format < FormatEnd && *Format != _T('$'); ++Format) + Modifiers |= 1 << (_tcschr(ModifierTable, _totlower(*Format)) - ModifierTable); + if (PathVarName) { /* $PATH: syntax - search the directories listed in the * specified environment variable for the file */ - LPTSTR PathVar; - FormatEnd[-1] = _T('\0'); + PTSTR PathVar; + ((PTSTR)FormatEnd)[-1] = _T('\0'); // FIXME: HACK! PathVar = GetEnvVar(PathVarName); - FormatEnd[-1] = _T(':'); + ((PTSTR)FormatEnd)[-1] = _T(':'); if (!PathVar || !SearchPath(PathVar, Result, NULL, ARRAYSIZE(FullPath), FullPath, NULL)) { @@ -1070,6 +1116,7 @@ GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, BOOL *)) /* Special case: If the variable is %0 and modifier characters are present, * use the batch file's path (which includes the .bat/.cmd extension) * rather than the actual %0 variable (which might not). */ + ASSERT(bc); _tcscpy(FullPath, bc->BatchFilePath); } else @@ -1103,7 +1150,7 @@ GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, BOOL *)) /* If it doesn't exist, just leave the name as it was given */ if (hFind != INVALID_HANDLE_VALUE) { - LPTSTR FixedComponent = w32fd.cFileName; + PTSTR FixedComponent = w32fd.cFileName; if (*w32fd.cAlternateFileName && ((Modifiers & M_SHORT) || !_tcsicmp(In, w32fd.cAlternateFileName))) { @@ -1194,7 +1241,7 @@ GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, BOOL *)) } if (Modifiers & (M_PATH | M_FULL)) { - memcpy(Out, &FixedPath[2], (char *)Filename - (char *)&FixedPath[2]); + memcpy(Out, &FixedPath[2], (ULONG_PTR)Filename - (ULONG_PTR)&FixedPath[2]); Out += Filename - &FixedPath[2]; } if (Modifiers & (M_NAME | M_FULL)) @@ -1217,18 +1264,20 @@ GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, BOOL *)) return Result; }
-static LPCTSTR -GetBatchVar(TCHAR *varName, UINT *varNameLen) +static PCTSTR +GetBatchVar( + IN PCTSTR varName, + OUT PUINT varNameLen) { - LPCTSTR ret; - TCHAR *varNameEnd; - BOOL dummy; + PCTSTR ret; + PCTSTR varNameEnd;
*varNameLen = 1;
- switch ( *varName ) + switch (*varName) { case _T('~'): + { varNameEnd = varName + 1; ret = GetEnhancedVar(&varNameEnd, FindArg); if (!ret) @@ -1238,6 +1287,7 @@ GetBatchVar(TCHAR *varName, UINT *varNameLen) } *varNameLen = varNameEnd - varName; return ret; + }
case _T('0'): case _T('1'): @@ -1249,7 +1299,13 @@ GetBatchVar(TCHAR *varName, UINT *varNameLen) case _T('7'): case _T('8'): case _T('9'): - return FindArg(*varName, &dummy); + { + BOOL dummy; + if (!FindArg(*varName, &ret, &dummy)) + return NULL; + else + return ret; + }
case _T('*'): /* Copy over the raw params (not including the batch file name) */ @@ -1423,37 +1479,53 @@ too_long: }
/* Search the list of FOR contexts for a variable */ -static LPTSTR -FindForVar(TCHAR Var, BOOL *IsParam0) +static BOOL +FindForVar( + IN TCHAR Var, + OUT PCTSTR* VarPtr, + OUT BOOL* IsParam0) { PFOR_CONTEXT Ctx;
+ *VarPtr = NULL; *IsParam0 = FALSE; + for (Ctx = fc; Ctx != NULL; Ctx = Ctx->prev) { if ((UINT)(Var - Ctx->firstvar) < Ctx->varcount) - return Ctx->values[Var - Ctx->firstvar]; + { + *VarPtr = Ctx->values[Var - Ctx->firstvar]; + return TRUE; + } } - return NULL; + return FALSE; }
BOOL -SubstituteForVars(TCHAR *Src, TCHAR *Dest) +SubstituteForVars( + IN PCTSTR Src, + OUT PTSTR Dest) { - TCHAR *DestEnd = &Dest[CMDLINE_LENGTH - 1]; + PTCHAR DestEnd = &Dest[CMDLINE_LENGTH - 1]; while (*Src) { if (Src[0] == _T('%')) { BOOL Dummy; - LPTSTR End = &Src[2]; - LPTSTR Value = NULL; + PCTSTR End = &Src[2]; + PCTSTR Value = NULL;
if (Src[1] == _T('~')) Value = GetEnhancedVar(&End, FindForVar);
- if (!Value) - Value = FindForVar(Src[1], &Dummy); + if (!Value && Src[1]) + { + if (FindForVar(Src[1], &Value, &Dummy) && !Value) + { + /* The variable is empty, return an empty string */ + Value = _T(""); + } + }
if (Value) { diff --git a/base/shell/cmd/cmd.h b/base/shell/cmd/cmd.h index b3c1c0f1d9d..ac2d7fddd8c 100644 --- a/base/shell/cmd/cmd.h +++ b/base/shell/cmd/cmd.h @@ -96,7 +96,12 @@ LPCTSTR GetEnvVarOrSpecial ( LPCTSTR varName ); VOID AddBreakHandler (VOID); VOID RemoveBreakHandler (VOID); BOOL SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim); -BOOL SubstituteForVars(TCHAR *Src, TCHAR *Dest); + +BOOL +SubstituteForVars( + IN PCTSTR Src, + OUT PTSTR Dest); + LPTSTR DoDelayedExpansion(LPTSTR Line); INT DoCommand(LPTSTR first, LPTSTR rest, struct _PARSED_COMMAND *Cmd); BOOL ReadLine(TCHAR *commandline, BOOL bMore);