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