[MKHIVE/USETUP]
[reactos.git] / reactos / 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 * Seperated 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 hardcode string to 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 DWORD Len = 0;
98 DWORD AllocLen = 1000;
99 LPTSTR Contents = cmd_alloc(AllocLen * sizeof(TCHAR));
100 if (!Contents)
101 return NULL;
102
103 while (_fgetts(Buffer, CMDLINE_LENGTH, InputFile))
104 {
105 DWORD CharsRead = _tcslen(Buffer);
106 while (Len + CharsRead >= AllocLen)
107 {
108 Contents = cmd_realloc(Contents, (AllocLen *= 2) * sizeof(TCHAR));
109 if (!Contents)
110 return NULL;
111 }
112 _tcscpy(&Contents[Len], Buffer);
113 Len += CharsRead;
114 }
115
116 Contents[Len] = _T('\0');
117 return Contents;
118 }
119
120 static INT ForF(PARSED_COMMAND *Cmd, LPTSTR List, TCHAR *Buffer)
121 {
122 LPTSTR Delims = _T(" \t");
123 TCHAR Eol = _T(';');
124 INT SkipLines = 0;
125 DWORD Tokens = (1 << 1);
126 BOOL RemainderVar = FALSE;
127 TCHAR StringQuote = _T('"');
128 TCHAR CommandQuote = _T('\'');
129 LPTSTR Variables[32];
130 TCHAR *Start, *End;
131 INT i;
132 INT Ret = 0;
133
134 if (Cmd->For.Params)
135 {
136 TCHAR Quote = 0;
137 TCHAR *Param = Cmd->For.Params;
138 if (*Param == _T('"') || *Param == _T('\''))
139 Quote = *Param++;
140
141 while (*Param && *Param != Quote)
142 {
143 if (*Param <= _T(' '))
144 {
145 Param++;
146 }
147 else if (_tcsnicmp(Param, _T("delims="), 7) == 0)
148 {
149 Param += 7;
150 /* delims=xxx: Specifies the list of characters that separate tokens */
151 Delims = Param;
152 while (*Param && *Param != Quote)
153 {
154 if (*Param == _T(' '))
155 {
156 TCHAR *FirstSpace = Param;
157 Param += _tcsspn(Param, _T(" "));
158 /* Exclude trailing spaces if this is not the last parameter */
159 if (*Param && *Param != Quote)
160 *FirstSpace = _T('\0');
161 break;
162 }
163 Param++;
164 }
165 if (*Param == Quote)
166 *Param++ = _T('\0');
167 }
168 else if (_tcsnicmp(Param, _T("eol="), 4) == 0)
169 {
170 Param += 4;
171 /* eol=c: Lines starting with this character (may be
172 * preceded by delimiters) are skipped. */
173 Eol = *Param;
174 if (Eol != _T('\0'))
175 Param++;
176 }
177 else if (_tcsnicmp(Param, _T("skip="), 5) == 0)
178 {
179 /* skip=n: Number of lines to skip at the beginning of each file */
180 SkipLines = _tcstol(Param + 5, &Param, 0);
181 if (SkipLines <= 0)
182 goto error;
183 }
184 else if (_tcsnicmp(Param, _T("tokens="), 7) == 0)
185 {
186 Param += 7;
187 /* tokens=x,y,m-n: List of token numbers (must be between
188 * 1 and 31) that will be assigned into variables. */
189 Tokens = 0;
190 while (*Param && *Param != Quote && *Param != _T('*'))
191 {
192 INT First = _tcstol(Param, &Param, 0);
193 INT Last = First;
194 if (First < 1)
195 goto error;
196 if (*Param == _T('-'))
197 {
198 /* It's a range of tokens */
199 Last = _tcstol(Param + 1, &Param, 0);
200 if (Last < First || Last > 31)
201 goto error;
202 }
203 Tokens |= (2 << Last) - (1 << First);
204
205 if (*Param != _T(','))
206 break;
207 Param++;
208 }
209 /* With an asterisk at the end, an additional variable
210 * will be created to hold the remainder of the line
211 * (after the last token specified). */
212 if (*Param == _T('*'))
213 {
214 RemainderVar = TRUE;
215 Param++;
216 }
217 }
218 else if (_tcsnicmp(Param, _T("useback"), 7) == 0)
219 {
220 Param += 7;
221 /* usebackq: Use alternate quote characters */
222 StringQuote = _T('\'');
223 CommandQuote = _T('`');
224 /* Can be written as either "useback" or "usebackq" */
225 if (_totlower(*Param) == _T('q'))
226 Param++;
227 }
228 else
229 {
230 error:
231 error_syntax(Param);
232 return 1;
233 }
234 }
235 }
236
237 /* Count how many variables will be set: one for each token,
238 * plus maybe one for the remainder */
239 fc->varcount = RemainderVar;
240 for (i = 1; i < 32; i++)
241 fc->varcount += (Tokens >> i & 1);
242 fc->values = Variables;
243
244 if (*List == StringQuote || *List == CommandQuote)
245 {
246 /* Treat the entire "list" as one single element */
247 Start = List;
248 End = &List[_tcslen(List)];
249 goto single_element;
250 }
251
252 End = List;
253 while (GetNextElement(&Start, &End))
254 {
255 FILE *InputFile;
256 LPTSTR FullInput, In, NextLine;
257 INT Skip;
258 single_element:
259
260 if (*Start == StringQuote && End[-1] == StringQuote)
261 {
262 /* Input given directly as a string */
263 End[-1] = _T('\0');
264 FullInput = cmd_dup(Start + 1);
265 }
266 else if (*Start == CommandQuote && End[-1] == CommandQuote)
267 {
268 /* Read input from a command */
269 End[-1] = _T('\0');
270 InputFile = _tpopen(Start + 1, _T("r"));
271 if (!InputFile)
272 {
273 error_bad_command(Start + 1);
274 return 1;
275 }
276 FullInput = ReadFileContents(InputFile, Buffer);
277 _pclose(InputFile);
278 }
279 else
280 {
281 /* Read input from a file */
282 TCHAR Temp = *End;
283 *End = _T('\0');
284 StripQuotes(Start);
285 InputFile = _tfopen(Start, _T("r"));
286 *End = Temp;
287 if (!InputFile)
288 {
289 error_sfile_not_found(Start);
290 return 1;
291 }
292 FullInput = ReadFileContents(InputFile, Buffer);
293 fclose(InputFile);
294 }
295
296 if (!FullInput)
297 {
298 error_out_of_memory();
299 return 1;
300 }
301
302 /* Loop over the input line by line */
303 In = FullInput;
304 Skip = SkipLines;
305 do
306 {
307 DWORD RemainingTokens = Tokens;
308 LPTSTR *CurVar = Variables;
309
310 NextLine = _tcschr(In, _T('\n'));
311 if (NextLine)
312 *NextLine++ = _T('\0');
313
314 if (--Skip >= 0)
315 continue;
316
317 /* Ignore lines where the first token starts with the eol character */
318 In += _tcsspn(In, Delims);
319 if (*In == Eol)
320 continue;
321
322 while ((RemainingTokens >>= 1) != 0)
323 {
324 /* Save pointer to this token in a variable if requested */
325 if (RemainingTokens & 1)
326 *CurVar++ = In;
327 /* Find end of token */
328 In += _tcscspn(In, Delims);
329 /* Nul-terminate it and advance to next token */
330 if (*In)
331 {
332 *In++ = _T('\0');
333 In += _tcsspn(In, Delims);
334 }
335 }
336 /* Save pointer to remainder of line */
337 *CurVar = In;
338
339 /* Don't run unless the line had enough tokens to fill at least one variable */
340 if (*Variables[0])
341 Ret = RunInstance(Cmd);
342 } while (!Exiting(Cmd) && (In = NextLine) != NULL);
343 cmd_free(FullInput);
344 }
345
346 return Ret;
347 }
348
349 /* FOR /L: Do a numeric loop */
350 static INT ForLoop(PARSED_COMMAND *Cmd, LPTSTR List, TCHAR *Buffer)
351 {
352 enum { START, STEP, END };
353 INT params[3] = { 0, 0, 0 };
354 INT i;
355 INT Ret = 0;
356
357 TCHAR *Start, *End = List;
358 for (i = 0; i < 3 && GetNextElement(&Start, &End); i++)
359 params[i] = _tcstol(Start, NULL, 0);
360
361 i = params[START];
362 while (!Exiting(Cmd) &&
363 (params[STEP] >= 0 ? (i <= params[END]) : (i >= params[END])))
364 {
365 _itot(i, Buffer, 10);
366 Ret = RunInstance(Cmd);
367 i += params[STEP];
368 }
369 return Ret;
370 }
371
372 /* Process a FOR in one directory. Stored in Buffer (up to BufPos) is a
373 * string which is prefixed to each element of the list. In a normal FOR
374 * it will be empty, but in FOR /R it will be the directory name. */
375 static INT ForDir(PARSED_COMMAND *Cmd, LPTSTR List, TCHAR *Buffer, TCHAR *BufPos)
376 {
377 TCHAR *Start, *End = List;
378 INT Ret = 0;
379 while (!Exiting(Cmd) && GetNextElement(&Start, &End))
380 {
381 if (BufPos + (End - Start) > &Buffer[CMDLINE_LENGTH])
382 continue;
383 memcpy(BufPos, Start, (End - Start) * sizeof(TCHAR));
384 BufPos[End - Start] = _T('\0');
385
386 if (_tcschr(BufPos, _T('?')) || _tcschr(BufPos, _T('*')))
387 {
388 WIN32_FIND_DATA w32fd;
389 HANDLE hFind;
390 TCHAR *FilePart;
391
392 StripQuotes(BufPos);
393 hFind = FindFirstFile(Buffer, &w32fd);
394 if (hFind == INVALID_HANDLE_VALUE)
395 continue;
396 FilePart = _tcsrchr(BufPos, _T('\\'));
397 FilePart = FilePart ? FilePart + 1 : BufPos;
398 do
399 {
400 if (w32fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
401 continue;
402 if (!(w32fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
403 != !(Cmd->For.Switches & FOR_DIRS))
404 continue;
405 if (_tcscmp(w32fd.cFileName, _T(".")) == 0 ||
406 _tcscmp(w32fd.cFileName, _T("..")) == 0)
407 continue;
408 _tcscpy(FilePart, w32fd.cFileName);
409 Ret = RunInstance(Cmd);
410 } while (!Exiting(Cmd) && FindNextFile(hFind, &w32fd));
411 FindClose(hFind);
412 }
413 else
414 {
415 Ret = RunInstance(Cmd);
416 }
417 }
418 return Ret;
419 }
420
421 /* FOR /R: Process a FOR in each directory of a tree, recursively. */
422 static INT ForRecursive(PARSED_COMMAND *Cmd, LPTSTR List, TCHAR *Buffer, TCHAR *BufPos)
423 {
424 HANDLE hFind;
425 WIN32_FIND_DATA w32fd;
426 INT Ret = 0;
427
428 if (BufPos[-1] != _T('\\'))
429 {
430 *BufPos++ = _T('\\');
431 *BufPos = _T('\0');
432 }
433
434 Ret = ForDir(Cmd, List, Buffer, BufPos);
435
436 _tcscpy(BufPos, _T("*"));
437 hFind = FindFirstFile(Buffer, &w32fd);
438 if (hFind == INVALID_HANDLE_VALUE)
439 return Ret;
440 do
441 {
442 if (!(w32fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
443 continue;
444 if (_tcscmp(w32fd.cFileName, _T(".")) == 0 ||
445 _tcscmp(w32fd.cFileName, _T("..")) == 0)
446 continue;
447 Ret = ForRecursive(Cmd, List, Buffer, _stpcpy(BufPos, w32fd.cFileName));
448 } while (!Exiting(Cmd) && FindNextFile(hFind, &w32fd));
449 FindClose(hFind);
450 return Ret;
451 }
452
453 BOOL
454 ExecuteFor(PARSED_COMMAND *Cmd)
455 {
456 TCHAR Buffer[CMDLINE_LENGTH]; /* Buffer to hold the variable value */
457 LPTSTR BufferPtr = Buffer;
458 LPFOR_CONTEXT lpNew;
459 INT Ret;
460 LPTSTR List = DoDelayedExpansion(Cmd->For.List);
461
462 if (!List)
463 return 1;
464
465 /* Create our FOR context */
466 lpNew = cmd_alloc(sizeof(FOR_CONTEXT));
467 if (!lpNew)
468 {
469 cmd_free(List);
470 return 1;
471 }
472 lpNew->prev = fc;
473 lpNew->firstvar = Cmd->For.Variable;
474 lpNew->varcount = 1;
475 lpNew->values = &BufferPtr;
476
477 Cmd->For.Context = lpNew;
478 fc = lpNew;
479
480 if (Cmd->For.Switches & FOR_F)
481 {
482 Ret = ForF(Cmd, List, Buffer);
483 }
484 else if (Cmd->For.Switches & FOR_LOOP)
485 {
486 Ret = ForLoop(Cmd, List, Buffer);
487 }
488 else if (Cmd->For.Switches & FOR_RECURSIVE)
489 {
490 DWORD Len = GetFullPathName(Cmd->For.Params ? Cmd->For.Params : _T("."),
491 MAX_PATH, Buffer, NULL);
492 Ret = ForRecursive(Cmd, List, Buffer, &Buffer[Len]);
493 }
494 else
495 {
496 Ret = ForDir(Cmd, List, Buffer, Buffer);
497 }
498
499 /* Remove our context, unless someone already did that */
500 if (fc == lpNew)
501 fc = lpNew->prev;
502
503 cmd_free(lpNew);
504 cmd_free(List);
505 return Ret;
506 }
507
508 /* EOF */