Files
reactos/base/shell/cmd/for.c
Timo Kreuzer 3413675193 [CMD] Clear global pointer to local variable after use
GCC 13 doesn't like if you keep a dangling pointer around.

C:/ReactOS/reactos/base/shell/cmd/for.c: In function 'ForF':
C:/ReactOS/reactos/base/shell/cmd/for.c:307:20: error: storing the address of local variable 'Variables' in '*fc.values' [-Werror=dangling-pointer=]
  307 |         fc->values = Variables;
      |         ~~~~~~~~~~~^~~~~~~~~~~
C:/ReactOS/reactos/base/shell/cmd/for.c:141:12: note: 'Variables' declared here
  141 |     LPTSTR Variables[32];
      |            ^~~~~~~~~
C:/ReactOS/reactos/base/shell/cmd/for.c:57:14: note: 'fc' declared here
   57 | PFOR_CONTEXT fc = NULL;
      |              ^~
2026-03-18 17:29:12 +02:00

670 lines
20 KiB
C

/*
* FOR.C - for internal batch command.
*
*
* History:
*
* 16-Jul-1998 (Hans B Pufal)
* Started.
*
* 16-Jul-1998 (John P Price)
* Separated commands into individual files.
*
* 19-Jul-1998 (Hans B Pufal)
* Implementation of FOR.
*
* 27-Jul-1998 (John P Price <linux-guru@gcfl.net>)
* Added config.h include.
*
* 20-Jan-1999 (Eric Kohl)
* Unicode and redirection safe!
*
* 01-Sep-1999 (Eric Kohl)
* Added help text.
*
* 23-Feb-2001 (Carl Nettelblad <cnettel@hem.passagen.se>)
* Implemented preservation of echo flag. Some other for related
* code in other files fixed, too.
*
* 28-Apr-2005 (Magnus Olsen <magnus@greatlord.com>)
* Remove all hardcoded strings in En.rc
*/
#include "precomp.h"
/* Enable this define for "buggy" Windows' CMD FOR-command compatibility.
* Currently, this enables the buggy behaviour of FOR /F token parsing. */
#define MSCMD_FOR_QUIRKS
/* FOR is a special command, so this function is only used for showing help now */
INT cmd_for(LPTSTR param)
{
TRACE("cmd_for(\'%s\')\n", debugstr_aw(param));
if (!_tcsncmp(param, _T("/?"), 2))
{
ConOutResPaging(TRUE, STRING_FOR_HELP1);
return 0;
}
ParseErrorEx(param);
return 1;
}
/* The stack of current FOR contexts.
* NULL when no FOR command is active */
PFOR_CONTEXT fc = NULL;
/* Get the next element of the FOR's list */
static BOOL GetNextElement(TCHAR **pStart, TCHAR **pEnd)
{
TCHAR *p = *pEnd;
BOOL InQuotes = FALSE;
while (_istspace(*p))
p++;
if (!*p)
return FALSE;
*pStart = p;
while (*p && (InQuotes || !_istspace(*p)))
InQuotes ^= (*p++ == _T('"'));
*pEnd = p;
return TRUE;
}
/* Execute a single instance of a FOR command */
/* Just run the command (variable expansion is done in DoDelayedExpansion) */
#define RunInstance(Cmd) \
ExecuteCommandWithEcho((Cmd)->Subcommands)
/* Check if this FOR should be terminated early */
#define Exiting(Cmd) \
/* Someone might have removed our context */ \
(bCtrlBreak || (fc != (Cmd)->For.Context))
/* Take also GOTO jumps into account */
#define ExitingOrGoto(Cmd) \
(Exiting(Cmd) || (bc && bc->current == NULL))
/* Read the contents of a text file into memory,
* dynamically allocating enough space to hold it all */
static LPTSTR ReadFileContents(FILE *InputFile, TCHAR *Buffer)
{
SIZE_T Len = 0;
SIZE_T AllocLen = 1000;
LPTSTR Contents = cmd_alloc(AllocLen * sizeof(TCHAR));
if (!Contents)
{
WARN("Cannot allocate memory for Contents!\n");
return NULL;
}
while (_fgetts(Buffer, CMDLINE_LENGTH, InputFile))
{
ULONG_PTR CharsRead = _tcslen(Buffer);
while (Len + CharsRead >= AllocLen)
{
LPTSTR OldContents = Contents;
Contents = cmd_realloc(Contents, (AllocLen *= 2) * sizeof(TCHAR));
if (!Contents)
{
WARN("Cannot reallocate memory for Contents!\n");
cmd_free(OldContents);
return NULL;
}
}
_tcscpy(&Contents[Len], Buffer);
Len += CharsRead;
}
Contents[Len] = _T('\0');
return Contents;
}
/* FOR /F: Parse the contents of each file */
static INT ForF(PARSED_COMMAND *Cmd, LPTSTR List, TCHAR *Buffer)
{
LPTSTR Delims = _T(" \t");
PTCHAR DelimsEndPtr = NULL;
TCHAR DelimsEndChr = _T('\0');
TCHAR Eol = _T(';');
INT SkipLines = 0;
DWORD TokensMask = (1 << 1);
#ifdef MSCMD_FOR_QUIRKS
DWORD NumTokens = 1;
DWORD RemainderVar = 0;
#else
DWORD NumTokens = 0;
#endif
TCHAR StringQuote = _T('"');
TCHAR CommandQuote = _T('\'');
LPTSTR Variables[32];
PTCHAR Start, End;
INT Ret = 0;
if (Cmd->For.Params)
{
TCHAR Quote = 0;
PTCHAR Param = Cmd->For.Params;
if (*Param == _T('"') || *Param == _T('\''))
Quote = *Param++;
while (*Param && *Param != Quote)
{
if (*Param <= _T(' '))
{
Param++;
}
else if (_tcsnicmp(Param, _T("delims="), 7) == 0)
{
Param += 7;
/*
* delims=xxx: Specifies the list of characters that separate tokens.
* This option does not cumulate: only the latest 'delims=' specification
* is taken into account.
*/
Delims = Param;
DelimsEndPtr = NULL;
while (*Param && *Param != Quote)
{
if (*Param == _T(' '))
{
PTCHAR FirstSpace = Param;
Param += _tcsspn(Param, _T(" "));
/* Exclude trailing spaces if this is not the last parameter */
if (*Param && *Param != Quote)
{
/* Save where the delimiters specification string ends */
DelimsEndPtr = FirstSpace;
}
break;
}
Param++;
}
if (*Param == Quote)
{
/* Save where the delimiters specification string ends */
DelimsEndPtr = Param++;
}
}
else if (_tcsnicmp(Param, _T("eol="), 4) == 0)
{
Param += 4;
/* eol=c: Lines starting with this character (may be
* preceded by delimiters) are skipped. */
Eol = *Param;
if (Eol != _T('\0'))
Param++;
}
else if (_tcsnicmp(Param, _T("skip="), 5) == 0)
{
/* skip=n: Number of lines to skip at the beginning of each file */
SkipLines = _tcstol(Param + 5, &Param, 0);
if (SkipLines <= 0)
goto error;
}
else if (_tcsnicmp(Param, _T("tokens="), 7) == 0)
{
#ifdef MSCMD_FOR_QUIRKS
DWORD NumToksInSpec = 0; // Number of tokens in this specification.
#endif
Param += 7;
/*
* tokens=x,y,m-n: List of token numbers (must be between 1 and 31)
* that will be assigned into variables. This option does not cumulate:
* only the latest 'tokens=' specification is taken into account.
*
* NOTE: In MSCMD_FOR_QUIRKS mode, for Windows' CMD compatibility,
* not all the tokens-state is reset. This leads to subtle bugs.
*/
TokensMask = 0;
#ifdef MSCMD_FOR_QUIRKS
NumToksInSpec = 0;
// Windows' CMD compatibility: bug: the asterisk-token's position is not reset!
// RemainderVar = 0;
#else
NumTokens = 0;
#endif
while (*Param && *Param != Quote && *Param != _T('*'))
{
INT First = _tcstol(Param, &Param, 0);
INT Last = First;
#ifdef MSCMD_FOR_QUIRKS
if (First < 1)
#else
if ((First < 1) || (First > 31))
#endif
goto error;
if (*Param == _T('-'))
{
/* It's a range of tokens */
Last = _tcstol(Param + 1, &Param, 0);
#ifdef MSCMD_FOR_QUIRKS
/* Ignore the range if the endpoints are not in correct order */
if (Last < 1)
#else
if ((Last < First) || (Last > 31))
#endif
goto error;
}
#ifdef MSCMD_FOR_QUIRKS
/* Ignore the range if the endpoints are not in correct order */
if ((First <= Last) && (Last <= 31))
{
#endif
TokensMask |= (2 << Last) - (1 << First);
#ifdef MSCMD_FOR_QUIRKS
NumToksInSpec += (Last - First + 1);
}
#endif
if (*Param != _T(','))
break;
Param++;
}
/* With an asterisk at the end, an additional variable
* will be created to hold the remainder of the line
* (after the last specified token). */
if (*Param == _T('*'))
{
#ifdef MSCMD_FOR_QUIRKS
RemainderVar = ++NumToksInSpec;
#else
++NumTokens;
#endif
Param++;
}
#ifdef MSCMD_FOR_QUIRKS
NumTokens = max(NumTokens, NumToksInSpec);
#endif
}
else if (_tcsnicmp(Param, _T("useback"), 7) == 0)
{
Param += 7;
/* usebackq: Use alternate quote characters */
StringQuote = _T('\'');
CommandQuote = _T('`');
/* Can be written as either "useback" or "usebackq" */
if (_totlower(*Param) == _T('q'))
Param++;
}
else
{
error:
error_syntax(Param);
return 1;
}
}
}
#ifdef MSCMD_FOR_QUIRKS
/* Windows' CMD compatibility: use the wrongly evaluated number of tokens */
fc->varcount = NumTokens;
/* Allocate a large enough variables array if needed */
if (NumTokens <= ARRAYSIZE(Variables))
{
fc->values = Variables;
}
else
{
fc->values = cmd_alloc(fc->varcount * sizeof(*fc->values));
if (!fc->values)
{
error_out_of_memory();
return 1;
}
}
#else
/* Count how many variables will be set: one for each token,
* plus maybe one for the remainder. */
fc->varcount = NumTokens;
for (NumTokens = 1; NumTokens < 32; ++NumTokens)
fc->varcount += (TokensMask >> NumTokens) & 1;
fc->values = Variables;
#endif
if (*List == StringQuote || *List == CommandQuote)
{
/* Treat the entire "list" as one single element */
Start = List;
End = &List[_tcslen(List)];
goto single_element;
}
/* Loop over each file */
End = List;
while (!ExitingOrGoto(Cmd) && GetNextElement(&Start, &End))
{
FILE* InputFile;
LPTSTR FullInput, In, NextLine;
INT Skip;
single_element:
if (*Start == StringQuote && End[-1] == StringQuote)
{
/* Input given directly as a string */
End[-1] = _T('\0');
FullInput = cmd_dup(Start + 1);
}
else if (*Start == CommandQuote && End[-1] == CommandQuote)
{
/*
* Read input from a command. We let the CRT do the ANSI/UNICODE conversion.
* NOTE: Should we do that, or instead read in binary mode and
* do the conversion by ourselves, using *OUR* current codepage??
*/
End[-1] = _T('\0');
InputFile = _tpopen(Start + 1, _T("r"));
if (!InputFile)
{
error_bad_command(Start + 1);
Ret = 1;
goto Quit;
}
FullInput = ReadFileContents(InputFile, Buffer);
_pclose(InputFile);
}
else
{
/* Read input from a file */
TCHAR Temp = *End;
*End = _T('\0');
StripQuotes(Start);
InputFile = _tfopen(Start, _T("r"));
*End = Temp;
if (!InputFile)
{
error_sfile_not_found(Start);
Ret = 1;
goto Quit;
}
FullInput = ReadFileContents(InputFile, Buffer);
fclose(InputFile);
}
if (!FullInput)
{
error_out_of_memory();
Ret = 1;
goto Quit;
}
/* Patch the delimiters string */
if (DelimsEndPtr)
{
DelimsEndChr = *DelimsEndPtr;
*DelimsEndPtr = _T('\0');
}
/* Loop over the input line by line */
for (In = FullInput, Skip = SkipLines;
!ExitingOrGoto(Cmd) && (In != NULL);
In = NextLine)
{
DWORD RemainingTokens = TokensMask;
LPTSTR* CurVar = fc->values;
ZeroMemory(fc->values, fc->varcount * sizeof(*fc->values));
#ifdef MSCMD_FOR_QUIRKS
NumTokens = fc->varcount;
#endif
NextLine = _tcschr(In, _T('\n'));
if (NextLine)
*NextLine++ = _T('\0');
if (--Skip >= 0)
continue;
/* Ignore lines where the first token starts with the eol character */
In += _tcsspn(In, Delims);
if (*In == Eol)
continue;
/* Loop as long as we have not reached the end of
* the line, and that we have tokens available.
* A maximum of 31 tokens will be enumerated. */
while (*In && ((RemainingTokens >>= 1) != 0))
{
/* Save pointer to this token in a variable if requested */
if (RemainingTokens & 1)
{
#ifdef MSCMD_FOR_QUIRKS
--NumTokens;
#endif
*CurVar++ = In;
}
/* Find end of token */
In += _tcscspn(In, Delims);
/* NULL-terminate it and advance to next token */
if (*In)
{
*In++ = _T('\0');
In += _tcsspn(In, Delims);
}
}
/* Save pointer to remainder of the line if we need to do so */
if (*In)
#ifdef MSCMD_FOR_QUIRKS
if (RemainderVar && (fc->varcount - NumTokens + 1 == RemainderVar))
#endif
{
/* NOTE: This sets fc->values[0] at least, if no tokens
* were initialized so far, since CurVar is initialized
* originally to point to fc->values. */
*CurVar = In;
}
/* Don't run unless we have at least one variable filled */
if (fc->values[0])
Ret = RunInstance(Cmd);
}
/* Restore the delimiters string */
if (DelimsEndPtr)
*DelimsEndPtr = DelimsEndChr;
cmd_free(FullInput);
}
Quit:
#ifdef MSCMD_FOR_QUIRKS
if (fc->values && (fc->values != Variables))
cmd_free(fc->values);
#endif
fc->values = NULL;
return Ret;
}
/* FOR /L: Do a numeric loop */
static INT ForLoop(PARSED_COMMAND *Cmd, LPTSTR List, TCHAR *Buffer)
{
enum { START, STEP, END };
INT params[3] = { 0, 0, 0 };
INT i;
INT Ret = 0;
TCHAR *Start, *End = List;
for (i = 0; i < 3 && GetNextElement(&Start, &End); ++i)
params[i] = _tcstol(Start, NULL, 0);
i = params[START];
/*
* Windows' CMD compatibility:
* Contrary to the other FOR-loops, FOR /L does not check
* whether a GOTO has been done, and will continue to loop.
*/
while (!Exiting(Cmd) &&
(params[STEP] >= 0 ? (i <= params[END]) : (i >= params[END])))
{
_itot(i, Buffer, 10);
Ret = RunInstance(Cmd);
i += params[STEP];
}
return Ret;
}
/* Process a FOR in one directory. Stored in Buffer (up to BufPos) is a
* string which is prefixed to each element of the list. In a normal FOR
* it will be empty, but in FOR /R it will be the directory name. */
static INT ForDir(PARSED_COMMAND *Cmd, LPTSTR List, TCHAR *Buffer, TCHAR *BufPos)
{
INT Ret = 0;
TCHAR *Start, *End = List;
while (!ExitingOrGoto(Cmd) && GetNextElement(&Start, &End))
{
if (BufPos + (End - Start) > &Buffer[CMDLINE_LENGTH])
continue;
memcpy(BufPos, Start, (End - Start) * sizeof(TCHAR));
BufPos[End - Start] = _T('\0');
if (_tcschr(BufPos, _T('?')) || _tcschr(BufPos, _T('*')))
{
WIN32_FIND_DATA w32fd;
HANDLE hFind;
TCHAR *FilePart;
StripQuotes(BufPos);
hFind = FindFirstFile(Buffer, &w32fd);
if (hFind == INVALID_HANDLE_VALUE)
continue;
FilePart = _tcsrchr(BufPos, _T('\\'));
FilePart = FilePart ? FilePart + 1 : BufPos;
do
{
if (w32fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
continue;
if (!(w32fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
!= !(Cmd->For.Switches & FOR_DIRS))
continue;
if (_tcscmp(w32fd.cFileName, _T(".")) == 0 ||
_tcscmp(w32fd.cFileName, _T("..")) == 0)
continue;
_tcscpy(FilePart, w32fd.cFileName);
Ret = RunInstance(Cmd);
} while (!ExitingOrGoto(Cmd) && FindNextFile(hFind, &w32fd));
FindClose(hFind);
}
else
{
Ret = RunInstance(Cmd);
}
}
return Ret;
}
/* FOR /R: Process a FOR in each directory of a tree, recursively */
static INT ForRecursive(PARSED_COMMAND *Cmd, LPTSTR List, TCHAR *Buffer, TCHAR *BufPos)
{
INT Ret = 0;
HANDLE hFind;
WIN32_FIND_DATA w32fd;
if (BufPos[-1] != _T('\\'))
{
*BufPos++ = _T('\\');
*BufPos = _T('\0');
}
Ret = ForDir(Cmd, List, Buffer, BufPos);
/* NOTE (We don't apply Windows' CMD compatibility here):
* Windows' CMD does not check whether a GOTO has been done,
* and will continue to loop. */
if (ExitingOrGoto(Cmd))
return Ret;
_tcscpy(BufPos, _T("*"));
hFind = FindFirstFile(Buffer, &w32fd);
if (hFind == INVALID_HANDLE_VALUE)
return Ret;
do
{
if (!(w32fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
continue;
if (_tcscmp(w32fd.cFileName, _T(".")) == 0 ||
_tcscmp(w32fd.cFileName, _T("..")) == 0)
continue;
Ret = ForRecursive(Cmd, List, Buffer, _stpcpy(BufPos, w32fd.cFileName));
/* NOTE (We don't apply Windows' CMD compatibility here):
* Windows' CMD does not check whether a GOTO has been done,
* and will continue to loop. */
} while (!ExitingOrGoto(Cmd) && FindNextFile(hFind, &w32fd));
FindClose(hFind);
return Ret;
}
INT
ExecuteFor(PARSED_COMMAND *Cmd)
{
INT Ret;
LPTSTR List;
PFOR_CONTEXT lpNew;
TCHAR Buffer[CMDLINE_LENGTH]; /* Buffer to hold the variable value */
LPTSTR BufferPtr = Buffer;
List = DoDelayedExpansion(Cmd->For.List);
if (!List)
return 1;
/* Create our FOR context */
lpNew = cmd_alloc(sizeof(FOR_CONTEXT));
if (!lpNew)
{
WARN("Cannot allocate memory for lpNew!\n");
cmd_free(List);
return 1;
}
lpNew->prev = fc;
lpNew->firstvar = Cmd->For.Variable;
lpNew->varcount = 1;
lpNew->values = &BufferPtr;
Cmd->For.Context = lpNew;
fc = lpNew;
/* Run the extended FOR syntax only if extensions are enabled */
if (bEnableExtensions)
{
if (Cmd->For.Switches & FOR_F)
{
Ret = ForF(Cmd, List, Buffer);
}
else if (Cmd->For.Switches & FOR_LOOP)
{
Ret = ForLoop(Cmd, List, Buffer);
}
else if (Cmd->For.Switches & FOR_RECURSIVE)
{
DWORD Len = GetFullPathName(Cmd->For.Params ? Cmd->For.Params : _T("."),
MAX_PATH, Buffer, NULL);
Ret = ForRecursive(Cmd, List, Buffer, &Buffer[Len]);
}
else
{
Ret = ForDir(Cmd, List, Buffer, Buffer);
}
}
else
{
Ret = ForDir(Cmd, List, Buffer, Buffer);
}
/* Remove our context, unless someone already did that */
if (fc == lpNew)
fc = lpNew->prev;
cmd_free(lpNew);
cmd_free(List);
return Ret;
}
/* EOF */