[SETUPLIB] Improve the bootloader 'validity' checks -- Addendum to f06734e5 (r74512).
[reactos.git] / base / shell / cmd / for.c
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 */