xref: /reactos/base/shell/cmd/for.c (revision cc439606)
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