[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.
This commit is contained in:
Hermès Bélusca-Maïto 2020-07-04 17:40:58 +02:00
parent 014efdf7e8
commit cb2a9c31a6
No known key found for this signature in database
GPG key ID: 3B2539C65E7B93D0
4 changed files with 146 additions and 58 deletions

View file

@ -74,29 +74,35 @@ TCHAR textline[BATCH_BUFFSIZE];
/* /*
* Returns a pointer to the n'th parameter of the current batch file. * 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 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'); INT n = Char - _T('0');
TRACE ("FindArg: (%d)\n", n); TRACE("FindArg: (%d)\n", n);
*ArgPtr = NULL;
if (n < 0 || n > 9) if (n < 0 || n > 9)
return NULL; return FALSE;
n = bc->shiftlevel[n]; n = bc->shiftlevel[n];
*IsParam0 = (n == 0); *IsParam0 = (n == 0);
pp = bc->params; pp = bc->params;
/* Step up the strings till we reach the end */ /* Step up the strings till we reach
/* or the one we want */ * the end or the one we want. */
while (*pp && n--) while (*pp && n--)
pp += _tcslen (pp) + 1; pp += _tcslen(pp) + 1;
return pp; *ArgPtr = pp;
return TRUE;
} }

View file

@ -44,7 +44,12 @@ extern BOOL bEcho; /* The echo flag */
extern TCHAR textline[BATCH_BUFFSIZE]; /* Buffer for reading Batch file lines */ 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 ExitBatch(VOID);
VOID ExitAllBatches(VOID); VOID ExitAllBatches(VOID);
INT Batch(LPTSTR, LPTSTR, LPTSTR, PARSED_COMMAND *); INT Batch(LPTSTR, LPTSTR, LPTSTR, PARSED_COMMAND *);

View file

@ -964,8 +964,10 @@ GetEnvVarOrSpecial(LPCTSTR varName)
} }
/* Handle the %~var syntax */ /* Handle the %~var syntax */
static LPTSTR static PCTSTR
GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, BOOL *)) GetEnhancedVar(
IN OUT PCTSTR* pFormat,
IN BOOL (*GetVar)(TCHAR, PCTSTR*, BOOL*))
{ {
static const TCHAR ModifierTable[] = _T("dpnxfsatz"); static const TCHAR ModifierTable[] = _T("dpnxfsatz");
enum { enum {
@ -980,28 +982,68 @@ GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, BOOL *))
M_SIZE = 256, /* Z: file size */ M_SIZE = 256, /* Z: file size */
} Modifiers = 0; } Modifiers = 0;
TCHAR *Format, *FormatEnd; PCTSTR Format, FormatEnd;
TCHAR *PathVarName = NULL; PCTSTR PathVarName = NULL;
LPTSTR Variable; PCTSTR Variable;
TCHAR *VarEnd; PCTSTR VarEnd;
BOOL VariableIsParam0; BOOL VariableIsParam0;
TCHAR FullPath[MAX_PATH]; TCHAR FullPath[MAX_PATH];
TCHAR FixedPath[MAX_PATH], *Filename, *Extension; TCHAR FixedPath[MAX_PATH];
PTCHAR Filename, Extension;
HANDLE hFind; HANDLE hFind;
WIN32_FIND_DATA w32fd; WIN32_FIND_DATA w32fd;
TCHAR *In, *Out; PTCHAR In, Out;
static TCHAR Result[CMDLINE_LENGTH]; static TCHAR Result[CMDLINE_LENGTH];
/* There is ambiguity between modifier characters and FOR variables; /* Check whether the current character is a recognized variable.
* the rule that cmd uses is to pick the longest possible match. * If it is not, then restore the previous one: there is indeed
* For example, if there is a %n variable, then out of %~anxnd, * ambiguity between modifier characters and FOR variables;
* %~anxn will be substituted rather than just %~an. */ * 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 */ /* First, go through as many modifier characters as possible */
FormatEnd = Format = *pFormat; FormatEnd = Format = *pFormat;
while (*FormatEnd && _tcschr(ModifierTable, _totlower(*FormatEnd))) while (*FormatEnd && _tcschr(ModifierTable, _totlower(*FormatEnd)))
FormatEnd++; ++FormatEnd;
if (*FormatEnd == _T('$')) if (*FormatEnd == _T('$'))
{ {
@ -1012,48 +1054,52 @@ GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, BOOL *))
return NULL; return NULL;
/* Must be immediately followed by the variable */ /* Must be immediately followed by the variable */
Variable = GetVar(*++FormatEnd, &VariableIsParam0); if (!GetVar(*++FormatEnd, &Variable, &VariableIsParam0))
if (!Variable)
return NULL; return NULL;
} }
else else
{ {
/* Backtrack if necessary to get a variable name match */ /* Backtrack if necessary to get a variable name match */
while (!(Variable = GetVar(*FormatEnd, &VariableIsParam0))) while (!GetVar(*FormatEnd, &Variable, &VariableIsParam0))
{ {
if (FormatEnd == Format) if (FormatEnd == Format)
return NULL; return NULL;
FormatEnd--; --FormatEnd;
} }
} }
for (; Format < FormatEnd && *Format != _T('$'); Format++)
Modifiers |= 1 << (_tcschr(ModifierTable, _totlower(*Format)) - ModifierTable);
*pFormat = FormatEnd + 1; *pFormat = FormatEnd + 1;
/* If the variable is empty, return an empty string */
if (!Variable || !*Variable)
return _T("");
/* Exclude the leading and trailing quotes */ /* Exclude the leading and trailing quotes */
VarEnd = &Variable[_tcslen(Variable)]; VarEnd = &Variable[_tcslen(Variable)];
if (*Variable == _T('"')) if (*Variable == _T('"'))
{ {
Variable++; ++Variable;
if (VarEnd > Variable && VarEnd[-1] == _T('"')) 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(""); return _T("");
memcpy(Result, Variable, (char *)VarEnd - (char *)Variable); memcpy(Result, Variable, (ULONG_PTR)VarEnd - (ULONG_PTR)Variable);
Result[VarEnd - Variable] = _T('\0'); 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) if (PathVarName)
{ {
/* $PATH: syntax - search the directories listed in the /* $PATH: syntax - search the directories listed in the
* specified environment variable for the file */ * specified environment variable for the file */
LPTSTR PathVar; PTSTR PathVar;
FormatEnd[-1] = _T('\0'); ((PTSTR)FormatEnd)[-1] = _T('\0'); // FIXME: HACK!
PathVar = GetEnvVar(PathVarName); PathVar = GetEnvVar(PathVarName);
FormatEnd[-1] = _T(':'); ((PTSTR)FormatEnd)[-1] = _T(':');
if (!PathVar || if (!PathVar ||
!SearchPath(PathVar, Result, NULL, ARRAYSIZE(FullPath), FullPath, NULL)) !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, /* Special case: If the variable is %0 and modifier characters are present,
* use the batch file's path (which includes the .bat/.cmd extension) * use the batch file's path (which includes the .bat/.cmd extension)
* rather than the actual %0 variable (which might not). */ * rather than the actual %0 variable (which might not). */
ASSERT(bc);
_tcscpy(FullPath, bc->BatchFilePath); _tcscpy(FullPath, bc->BatchFilePath);
} }
else 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 it doesn't exist, just leave the name as it was given */
if (hFind != INVALID_HANDLE_VALUE) if (hFind != INVALID_HANDLE_VALUE)
{ {
LPTSTR FixedComponent = w32fd.cFileName; PTSTR FixedComponent = w32fd.cFileName;
if (*w32fd.cAlternateFileName && if (*w32fd.cAlternateFileName &&
((Modifiers & M_SHORT) || !_tcsicmp(In, 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)) 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]; Out += Filename - &FixedPath[2];
} }
if (Modifiers & (M_NAME | M_FULL)) if (Modifiers & (M_NAME | M_FULL))
@ -1217,18 +1264,20 @@ GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, BOOL *))
return Result; return Result;
} }
static LPCTSTR static PCTSTR
GetBatchVar(TCHAR *varName, UINT *varNameLen) GetBatchVar(
IN PCTSTR varName,
OUT PUINT varNameLen)
{ {
LPCTSTR ret; PCTSTR ret;
TCHAR *varNameEnd; PCTSTR varNameEnd;
BOOL dummy;
*varNameLen = 1; *varNameLen = 1;
switch ( *varName ) switch (*varName)
{ {
case _T('~'): case _T('~'):
{
varNameEnd = varName + 1; varNameEnd = varName + 1;
ret = GetEnhancedVar(&varNameEnd, FindArg); ret = GetEnhancedVar(&varNameEnd, FindArg);
if (!ret) if (!ret)
@ -1238,6 +1287,7 @@ GetBatchVar(TCHAR *varName, UINT *varNameLen)
} }
*varNameLen = varNameEnd - varName; *varNameLen = varNameEnd - varName;
return ret; return ret;
}
case _T('0'): case _T('0'):
case _T('1'): case _T('1'):
@ -1249,7 +1299,13 @@ GetBatchVar(TCHAR *varName, UINT *varNameLen)
case _T('7'): case _T('7'):
case _T('8'): case _T('8'):
case _T('9'): case _T('9'):
return FindArg(*varName, &dummy); {
BOOL dummy;
if (!FindArg(*varName, &ret, &dummy))
return NULL;
else
return ret;
}
case _T('*'): case _T('*'):
/* Copy over the raw params (not including the batch file name) */ /* 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 */ /* Search the list of FOR contexts for a variable */
static LPTSTR static BOOL
FindForVar(TCHAR Var, BOOL *IsParam0) FindForVar(
IN TCHAR Var,
OUT PCTSTR* VarPtr,
OUT BOOL* IsParam0)
{ {
PFOR_CONTEXT Ctx; PFOR_CONTEXT Ctx;
*VarPtr = NULL;
*IsParam0 = FALSE; *IsParam0 = FALSE;
for (Ctx = fc; Ctx != NULL; Ctx = Ctx->prev) for (Ctx = fc; Ctx != NULL; Ctx = Ctx->prev)
{ {
if ((UINT)(Var - Ctx->firstvar) < Ctx->varcount) 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 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) while (*Src)
{ {
if (Src[0] == _T('%')) if (Src[0] == _T('%'))
{ {
BOOL Dummy; BOOL Dummy;
LPTSTR End = &Src[2]; PCTSTR End = &Src[2];
LPTSTR Value = NULL; PCTSTR Value = NULL;
if (Src[1] == _T('~')) if (Src[1] == _T('~'))
Value = GetEnhancedVar(&End, FindForVar); Value = GetEnhancedVar(&End, FindForVar);
if (!Value) if (!Value && Src[1])
Value = FindForVar(Src[1], &Dummy); {
if (FindForVar(Src[1], &Value, &Dummy) && !Value)
{
/* The variable is empty, return an empty string */
Value = _T("");
}
}
if (Value) if (Value)
{ {

View file

@ -96,7 +96,12 @@ LPCTSTR GetEnvVarOrSpecial ( LPCTSTR varName );
VOID AddBreakHandler (VOID); VOID AddBreakHandler (VOID);
VOID RemoveBreakHandler (VOID); VOID RemoveBreakHandler (VOID);
BOOL SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim); 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); LPTSTR DoDelayedExpansion(LPTSTR Line);
INT DoCommand(LPTSTR first, LPTSTR rest, struct _PARSED_COMMAND *Cmd); INT DoCommand(LPTSTR first, LPTSTR rest, struct _PARSED_COMMAND *Cmd);
BOOL ReadLine(TCHAR *commandline, BOOL bMore); BOOL ReadLine(TCHAR *commandline, BOOL bMore);