1 /* 2 * FOR.C - for internal batch command. 3 * 4 * 5 * History: 6 * 7 * 16-Jul-1998 (Hans B Pufal) 8 * Started. 9 * 10 * 16-Jul-1998 (John P Price) 11 * Separated commands into individual files. 12 * 13 * 19-Jul-1998 (Hans B Pufal) 14 * Implementation of FOR. 15 * 16 * 27-Jul-1998 (John P Price <linux-guru@gcfl.net>) 17 * Added config.h include. 18 * 19 * 20-Jan-1999 (Eric Kohl) 20 * Unicode and redirection safe! 21 * 22 * 01-Sep-1999 (Eric Kohl) 23 * Added help text. 24 * 25 * 23-Feb-2001 (Carl Nettelblad <cnettel@hem.passagen.se>) 26 * Implemented preservation of echo flag. Some other for related 27 * code in other files fixed, too. 28 * 29 * 28-Apr-2005 (Magnus Olsen <magnus@greatlord.com>) 30 * Remove all hardcoded strings in En.rc 31 */ 32 33 #include "precomp.h" 34 35 36 /* FOR is a special command, so this function is only used for showing help now */ 37 INT cmd_for (LPTSTR param) 38 { 39 TRACE ("cmd_for (\'%s\')\n", debugstr_aw(param)); 40 41 if (!_tcsncmp (param, _T("/?"), 2)) 42 { 43 ConOutResPaging(TRUE,STRING_FOR_HELP1); 44 return 0; 45 } 46 47 error_syntax(param); 48 return 1; 49 } 50 51 /* The stack of current FOR contexts. 52 * NULL when no FOR command is active */ 53 LPFOR_CONTEXT fc = NULL; 54 55 /* Get the next element of the FOR's list */ 56 static BOOL GetNextElement(TCHAR **pStart, TCHAR **pEnd) 57 { 58 TCHAR *p = *pEnd; 59 BOOL InQuotes = FALSE; 60 while (_istspace(*p)) 61 p++; 62 if (!*p) 63 return FALSE; 64 *pStart = p; 65 while (*p && (InQuotes || !_istspace(*p))) 66 InQuotes ^= (*p++ == _T('"')); 67 *pEnd = p; 68 return TRUE; 69 } 70 71 /* Execute a single instance of a FOR command */ 72 static INT RunInstance(PARSED_COMMAND *Cmd) 73 { 74 if (bEcho && !bDisableBatchEcho && Cmd->Subcommands->Type != C_QUIET) 75 { 76 if (!bIgnoreEcho) 77 ConOutChar(_T('\n')); 78 PrintPrompt(); 79 EchoCommand(Cmd->Subcommands); 80 ConOutChar(_T('\n')); 81 } 82 /* Just run the command (variable expansion is done in DoDelayedExpansion) */ 83 return ExecuteCommand(Cmd->Subcommands); 84 } 85 86 /* Check if this FOR should be terminated early */ 87 static BOOL Exiting(PARSED_COMMAND *Cmd) 88 { 89 /* Someone might have removed our context */ 90 return bCtrlBreak || fc != Cmd->For.Context; 91 } 92 93 /* Read the contents of a text file into memory, 94 * dynamically allocating enough space to hold it all */ 95 static LPTSTR ReadFileContents(FILE *InputFile, TCHAR *Buffer) 96 { 97 SIZE_T Len = 0; 98 SIZE_T AllocLen = 1000; 99 100 LPTSTR Contents = cmd_alloc(AllocLen * sizeof(TCHAR)); 101 if (!Contents) 102 { 103 WARN("Cannot allocate memory for Contents!\n"); 104 return NULL; 105 } 106 107 while (_fgetts(Buffer, CMDLINE_LENGTH, InputFile)) 108 { 109 ULONG_PTR CharsRead = _tcslen(Buffer); 110 while (Len + CharsRead >= AllocLen) 111 { 112 LPTSTR OldContents = Contents; 113 Contents = cmd_realloc(Contents, (AllocLen *= 2) * sizeof(TCHAR)); 114 if (!Contents) 115 { 116 WARN("Cannot reallocate memory for Contents!\n"); 117 cmd_free(OldContents); 118 return NULL; 119 } 120 } 121 _tcscpy(&Contents[Len], Buffer); 122 Len += CharsRead; 123 } 124 125 Contents[Len] = _T('\0'); 126 return Contents; 127 } 128 129 static INT ForF(PARSED_COMMAND *Cmd, LPTSTR List, TCHAR *Buffer) 130 { 131 LPTSTR Delims = _T(" \t"); 132 TCHAR Eol = _T(';'); 133 INT SkipLines = 0; 134 DWORD Tokens = (1 << 1); 135 BOOL RemainderVar = FALSE; 136 TCHAR StringQuote = _T('"'); 137 TCHAR CommandQuote = _T('\''); 138 LPTSTR Variables[32]; 139 TCHAR *Start, *End; 140 INT i; 141 INT Ret = 0; 142 143 if (Cmd->For.Params) 144 { 145 TCHAR Quote = 0; 146 TCHAR *Param = Cmd->For.Params; 147 if (*Param == _T('"') || *Param == _T('\'')) 148 Quote = *Param++; 149 150 while (*Param && *Param != Quote) 151 { 152 if (*Param <= _T(' ')) 153 { 154 Param++; 155 } 156 else if (_tcsnicmp(Param, _T("delims="), 7) == 0) 157 { 158 Param += 7; 159 /* delims=xxx: Specifies the list of characters that separate tokens */ 160 Delims = Param; 161 while (*Param && *Param != Quote) 162 { 163 if (*Param == _T(' ')) 164 { 165 TCHAR *FirstSpace = Param; 166 Param += _tcsspn(Param, _T(" ")); 167 /* Exclude trailing spaces if this is not the last parameter */ 168 if (*Param && *Param != Quote) 169 *FirstSpace = _T('\0'); 170 break; 171 } 172 Param++; 173 } 174 if (*Param == Quote) 175 *Param++ = _T('\0'); 176 } 177 else if (_tcsnicmp(Param, _T("eol="), 4) == 0) 178 { 179 Param += 4; 180 /* eol=c: Lines starting with this character (may be 181 * preceded by delimiters) are skipped. */ 182 Eol = *Param; 183 if (Eol != _T('\0')) 184 Param++; 185 } 186 else if (_tcsnicmp(Param, _T("skip="), 5) == 0) 187 { 188 /* skip=n: Number of lines to skip at the beginning of each file */ 189 SkipLines = _tcstol(Param + 5, &Param, 0); 190 if (SkipLines <= 0) 191 goto error; 192 } 193 else if (_tcsnicmp(Param, _T("tokens="), 7) == 0) 194 { 195 Param += 7; 196 /* tokens=x,y,m-n: List of token numbers (must be between 197 * 1 and 31) that will be assigned into variables. */ 198 Tokens = 0; 199 while (*Param && *Param != Quote && *Param != _T('*')) 200 { 201 INT First = _tcstol(Param, &Param, 0); 202 INT Last = First; 203 if (First < 1) 204 goto error; 205 if (*Param == _T('-')) 206 { 207 /* It's a range of tokens */ 208 Last = _tcstol(Param + 1, &Param, 0); 209 if (Last < First || Last > 31) 210 goto error; 211 } 212 Tokens |= (2 << Last) - (1 << First); 213 214 if (*Param != _T(',')) 215 break; 216 Param++; 217 } 218 /* With an asterisk at the end, an additional variable 219 * will be created to hold the remainder of the line 220 * (after the last token specified). */ 221 if (*Param == _T('*')) 222 { 223 RemainderVar = TRUE; 224 Param++; 225 } 226 } 227 else if (_tcsnicmp(Param, _T("useback"), 7) == 0) 228 { 229 Param += 7; 230 /* usebackq: Use alternate quote characters */ 231 StringQuote = _T('\''); 232 CommandQuote = _T('`'); 233 /* Can be written as either "useback" or "usebackq" */ 234 if (_totlower(*Param) == _T('q')) 235 Param++; 236 } 237 else 238 { 239 error: 240 error_syntax(Param); 241 return 1; 242 } 243 } 244 } 245 246 /* Count how many variables will be set: one for each token, 247 * plus maybe one for the remainder */ 248 fc->varcount = RemainderVar; 249 for (i = 1; i < 32; i++) 250 fc->varcount += (Tokens >> i & 1); 251 fc->values = Variables; 252 253 if (*List == StringQuote || *List == CommandQuote) 254 { 255 /* Treat the entire "list" as one single element */ 256 Start = List; 257 End = &List[_tcslen(List)]; 258 goto single_element; 259 } 260 261 End = List; 262 while (GetNextElement(&Start, &End)) 263 { 264 FILE *InputFile; 265 LPTSTR FullInput, In, NextLine; 266 INT Skip; 267 single_element: 268 269 if (*Start == StringQuote && End[-1] == StringQuote) 270 { 271 /* Input given directly as a string */ 272 End[-1] = _T('\0'); 273 FullInput = cmd_dup(Start + 1); 274 } 275 else if (*Start == CommandQuote && End[-1] == CommandQuote) 276 { 277 /* Read input from a command */ 278 End[-1] = _T('\0'); 279 InputFile = _tpopen(Start + 1, _T("r")); 280 if (!InputFile) 281 { 282 error_bad_command(Start + 1); 283 return 1; 284 } 285 FullInput = ReadFileContents(InputFile, Buffer); 286 _pclose(InputFile); 287 } 288 else 289 { 290 /* Read input from a file */ 291 TCHAR Temp = *End; 292 *End = _T('\0'); 293 StripQuotes(Start); 294 InputFile = _tfopen(Start, _T("r")); 295 *End = Temp; 296 if (!InputFile) 297 { 298 error_sfile_not_found(Start); 299 return 1; 300 } 301 FullInput = ReadFileContents(InputFile, Buffer); 302 fclose(InputFile); 303 } 304 305 if (!FullInput) 306 { 307 error_out_of_memory(); 308 return 1; 309 } 310 311 /* Loop over the input line by line */ 312 In = FullInput; 313 Skip = SkipLines; 314 do 315 { 316 DWORD RemainingTokens = Tokens; 317 LPTSTR *CurVar = Variables; 318 319 NextLine = _tcschr(In, _T('\n')); 320 if (NextLine) 321 *NextLine++ = _T('\0'); 322 323 if (--Skip >= 0) 324 continue; 325 326 /* Ignore lines where the first token starts with the eol character */ 327 In += _tcsspn(In, Delims); 328 if (*In == Eol) 329 continue; 330 331 while ((RemainingTokens >>= 1) != 0) 332 { 333 /* Save pointer to this token in a variable if requested */ 334 if (RemainingTokens & 1) 335 *CurVar++ = In; 336 /* Find end of token */ 337 In += _tcscspn(In, Delims); 338 /* Nul-terminate it and advance to next token */ 339 if (*In) 340 { 341 *In++ = _T('\0'); 342 In += _tcsspn(In, Delims); 343 } 344 } 345 /* Save pointer to remainder of line */ 346 *CurVar = In; 347 348 /* Don't run unless the line had enough tokens to fill at least one variable */ 349 if (*Variables[0]) 350 Ret = RunInstance(Cmd); 351 } while (!Exiting(Cmd) && (In = NextLine) != NULL); 352 cmd_free(FullInput); 353 } 354 355 return Ret; 356 } 357 358 /* FOR /L: Do a numeric loop */ 359 static INT ForLoop(PARSED_COMMAND *Cmd, LPTSTR List, TCHAR *Buffer) 360 { 361 enum { START, STEP, END }; 362 INT params[3] = { 0, 0, 0 }; 363 INT i; 364 INT Ret = 0; 365 366 TCHAR *Start, *End = List; 367 for (i = 0; i < 3 && GetNextElement(&Start, &End); i++) 368 params[i] = _tcstol(Start, NULL, 0); 369 370 i = params[START]; 371 while (!Exiting(Cmd) && 372 (params[STEP] >= 0 ? (i <= params[END]) : (i >= params[END]))) 373 { 374 _itot(i, Buffer, 10); 375 Ret = RunInstance(Cmd); 376 i += params[STEP]; 377 } 378 return Ret; 379 } 380 381 /* Process a FOR in one directory. Stored in Buffer (up to BufPos) is a 382 * string which is prefixed to each element of the list. In a normal FOR 383 * it will be empty, but in FOR /R it will be the directory name. */ 384 static INT ForDir(PARSED_COMMAND *Cmd, LPTSTR List, TCHAR *Buffer, TCHAR *BufPos) 385 { 386 TCHAR *Start, *End = List; 387 INT Ret = 0; 388 while (!Exiting(Cmd) && GetNextElement(&Start, &End)) 389 { 390 if (BufPos + (End - Start) > &Buffer[CMDLINE_LENGTH]) 391 continue; 392 memcpy(BufPos, Start, (End - Start) * sizeof(TCHAR)); 393 BufPos[End - Start] = _T('\0'); 394 395 if (_tcschr(BufPos, _T('?')) || _tcschr(BufPos, _T('*'))) 396 { 397 WIN32_FIND_DATA w32fd; 398 HANDLE hFind; 399 TCHAR *FilePart; 400 401 StripQuotes(BufPos); 402 hFind = FindFirstFile(Buffer, &w32fd); 403 if (hFind == INVALID_HANDLE_VALUE) 404 continue; 405 FilePart = _tcsrchr(BufPos, _T('\\')); 406 FilePart = FilePart ? FilePart + 1 : BufPos; 407 do 408 { 409 if (w32fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) 410 continue; 411 if (!(w32fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 412 != !(Cmd->For.Switches & FOR_DIRS)) 413 continue; 414 if (_tcscmp(w32fd.cFileName, _T(".")) == 0 || 415 _tcscmp(w32fd.cFileName, _T("..")) == 0) 416 continue; 417 _tcscpy(FilePart, w32fd.cFileName); 418 Ret = RunInstance(Cmd); 419 } while (!Exiting(Cmd) && FindNextFile(hFind, &w32fd)); 420 FindClose(hFind); 421 } 422 else 423 { 424 Ret = RunInstance(Cmd); 425 } 426 } 427 return Ret; 428 } 429 430 /* FOR /R: Process a FOR in each directory of a tree, recursively. */ 431 static INT ForRecursive(PARSED_COMMAND *Cmd, LPTSTR List, TCHAR *Buffer, TCHAR *BufPos) 432 { 433 HANDLE hFind; 434 WIN32_FIND_DATA w32fd; 435 INT Ret = 0; 436 437 if (BufPos[-1] != _T('\\')) 438 { 439 *BufPos++ = _T('\\'); 440 *BufPos = _T('\0'); 441 } 442 443 Ret = ForDir(Cmd, List, Buffer, BufPos); 444 445 _tcscpy(BufPos, _T("*")); 446 hFind = FindFirstFile(Buffer, &w32fd); 447 if (hFind == INVALID_HANDLE_VALUE) 448 return Ret; 449 do 450 { 451 if (!(w32fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) 452 continue; 453 if (_tcscmp(w32fd.cFileName, _T(".")) == 0 || 454 _tcscmp(w32fd.cFileName, _T("..")) == 0) 455 continue; 456 Ret = ForRecursive(Cmd, List, Buffer, _stpcpy(BufPos, w32fd.cFileName)); 457 } while (!Exiting(Cmd) && FindNextFile(hFind, &w32fd)); 458 FindClose(hFind); 459 return Ret; 460 } 461 462 INT 463 ExecuteFor(PARSED_COMMAND *Cmd) 464 { 465 TCHAR Buffer[CMDLINE_LENGTH]; /* Buffer to hold the variable value */ 466 LPTSTR BufferPtr = Buffer; 467 LPFOR_CONTEXT lpNew; 468 INT Ret; 469 LPTSTR List = DoDelayedExpansion(Cmd->For.List); 470 471 if (!List) 472 return 1; 473 474 /* Create our FOR context */ 475 lpNew = cmd_alloc(sizeof(FOR_CONTEXT)); 476 if (!lpNew) 477 { 478 WARN("Cannot allocate memory for lpNew!\n"); 479 cmd_free(List); 480 return 1; 481 } 482 lpNew->prev = fc; 483 lpNew->firstvar = Cmd->For.Variable; 484 lpNew->varcount = 1; 485 lpNew->values = &BufferPtr; 486 487 Cmd->For.Context = lpNew; 488 fc = lpNew; 489 490 if (Cmd->For.Switches & FOR_F) 491 { 492 Ret = ForF(Cmd, List, Buffer); 493 } 494 else if (Cmd->For.Switches & FOR_LOOP) 495 { 496 Ret = ForLoop(Cmd, List, Buffer); 497 } 498 else if (Cmd->For.Switches & FOR_RECURSIVE) 499 { 500 DWORD Len = GetFullPathName(Cmd->For.Params ? Cmd->For.Params : _T("."), 501 MAX_PATH, Buffer, NULL); 502 Ret = ForRecursive(Cmd, List, Buffer, &Buffer[Len]); 503 } 504 else 505 { 506 Ret = ForDir(Cmd, List, Buffer, Buffer); 507 } 508 509 /* Remove our context, unless someone already did that */ 510 if (fc == lpNew) 511 fc = lpNew->prev; 512 513 cmd_free(lpNew); 514 cmd_free(List); 515 return Ret; 516 } 517 518 /* EOF */ 519