Something went wrong. Try again.
Reactos
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669/* * 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
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;}
INTExecuteFor(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 */