update for HEAD-2003091401
[reactos.git] / subsys / system / cmd / batch.c
1 /* $Id$
2  *
3  *  BATCH.C - batch file processor for CMD.EXE.
4  *
5  *
6  *  History:
7  *
8  *    ??/??/?? (Evan Jeffrey)
9  *        started.
10  *
11  *    15 Jul 1995 (Tim Norman)
12  *        modes and bugfixes.
13  *
14  *    08 Aug 1995 (Matt Rains)
15  *        i have cleaned up the source code. changes now bring this
16  *        source into guidelines for recommended programming practice.
17  *
18  *        i have added some constants to help making changes easier.
19  *
20  *    29 Jan 1996 (Steffan Kaiser)
21  *        made a few cosmetic changes
22  *
23  *    05 Feb 1996 (Tim Norman)
24  *        changed to comply with new first/rest calling scheme
25  *
26  *    14 Jun 1997 (Steffen Kaiser)
27  *        bug fixes.  added error level expansion %?.  ctrl-break handling
28  *
29  *    16 Jul 1998 (Hans B Pufal)
30  *        Totally reorganised in conjunction with COMMAND.C (cf) to
31  *        implement proper BATCH file nesting and other improvements.
32  *
33  *    16 Jul 1998 (John P Price <linux-guru@gcfl.net>)
34  *        Seperated commands into individual files.
35  *
36  *    19 Jul 1998 (Hans B Pufal) [HBP_001]
37  *        Preserve state of echo flag across batch calls.
38  *
39  *    19 Jul 1998 (Hans B Pufal) [HBP_002]
40  *        Implementation of FOR command
41  *
42  *    20-Jul-1998 (John P Price <linux-guru@gcfl.net>)
43  *        added error checking after malloc calls
44  *
45  *    27-Jul-1998 (John P Price <linux-guru@gcfl.net>)
46  *        added config.h include
47  *
48  *    02-Aug-1998 (Hans B Pufal) [HBP_003]
49  *        Fixed bug in ECHO flag restoration at exit from batch file
50  *
51  *    26-Jan-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
52  *        Replaced CRT io functions by Win32 io functions.
53  *        Unicode safe!
54  *    
55  *    23-Feb-2001 (Carl Nettelblad <cnettel@hem.passagen.es>)
56  *        Fixes made to get "for" working.
57  */
58
59 #include "config.h"
60
61 #include <windows.h>
62 #include <tchar.h>
63 #include <string.h>
64 #include <stdlib.h>
65 #include <ctype.h>
66
67 #include "cmd.h"
68 #include "batch.h"
69
70
71 /* The stack of current batch contexts.
72  * NULL when no batch is active
73  */
74 LPBATCH_CONTEXT bc = NULL;
75
76 BOOL bEcho = TRUE;          /* The echo flag */
77
78
79
80 /* Buffer for reading Batch file lines */
81 TCHAR textline[BATCH_BUFFSIZE];
82
83
84 /*
85  * Returns a pointer to the n'th parameter of the current batch file.
86  * If no such parameter exists returns pointer to empty string.
87  * If no batch file is current, returns NULL
88  *
89  */
90
91 LPTSTR FindArg (INT n)
92 {
93         LPTSTR pp;
94
95 #ifdef _DEBUG
96         DebugPrintf (_T("FindArg: (%d)\n"), n);
97 #endif
98
99         if (bc == NULL)
100                 return NULL;
101
102         n += bc->shiftlevel;
103         pp = bc->params;
104
105         /* Step up the strings till we reach the end */
106         /* or the one we want */
107         while (*pp && n--)
108                 pp += _tcslen (pp) + 1;
109
110         return pp;
111 }
112
113
114 /*
115  * Batch_params builds a parameter list in newlay allocated memory.
116  * The parameters consist of null terminated strings with a final
117  * NULL character signalling the end of the parameters.
118  *
119 */
120
121 LPTSTR BatchParams (LPTSTR s1, LPTSTR s2)
122 {
123         LPTSTR dp = (LPTSTR)malloc ((_tcslen(s1) + _tcslen(s2) + 3) * sizeof (TCHAR));
124
125         /* JPP 20-Jul-1998 added error checking */
126         if (dp == NULL)
127         {
128                 error_out_of_memory();
129                 return NULL;
130         }
131
132         if (s1 && *s1)
133         {
134                 s1 = stpcpy (dp, s1);
135                 *s1++ = _T('\0');
136         }
137         else
138                 s1 = dp;
139
140         while (*s2)
141         {
142                 if (_istspace (*s2) || _tcschr (_T(",;"), *s2))
143                 {
144                         *s1++ = _T('\0');
145                         s2++;
146                         while (*s2 && _tcschr (_T(" ,;"), *s2))
147                                 s2++;
148                         continue;
149                 }
150
151                 if ((*s2 == _T('"')) || (*s2 == _T('\'')))
152                 {
153                         TCHAR st = *s2;
154
155                         do
156                                 *s1++ = *s2++;
157                         while (*s2 && (*s2 != st));
158                 }
159
160                 *s1++ = *s2++;
161         }
162
163         *s1++ = _T('\0');
164         *s1 = _T('\0');
165
166         return dp;
167 }
168
169
170 /*
171  * If a batch file is current, exits it, freeing the context block and
172  * chaining back to the previous one.
173  *
174  * If no new batch context is found, sets ECHO back ON.
175  *
176  * If the parameter is non-null or not empty, it is printed as an exit
177  * message
178  */
179
180 VOID ExitBatch (LPTSTR msg)
181 {
182 #ifdef _DEBUG
183         DebugPrintf (_T("ExitBatch: (\'%s\')\n"), msg);
184 #endif
185
186         if (bc != NULL)
187         {
188                 LPBATCH_CONTEXT t = bc;
189
190                 if (bc->hBatchFile)
191                 {
192                         CloseHandle (bc->hBatchFile);
193                         bc->hBatchFile = INVALID_HANDLE_VALUE;
194                 }
195
196                 if (bc->params)
197                         free(bc->params);
198
199                 if (bc->forproto)
200                         free(bc->forproto);
201
202                 if (bc->ffind)
203                         free(bc->ffind);
204
205                 /* Preserve echo state across batch calls */
206                 bEcho = bc->bEcho;
207
208                 bc = bc->prev;
209                 free(t);
210         }
211
212         if (msg && *msg)
213                 ConOutPrintf (_T("%s\n"), msg);
214 }
215
216
217 /*
218  * Start batch file execution
219  *
220  * The firstword parameter is the full filename of the batch file.
221  *
222  */
223
224 BOOL Batch (LPTSTR fullname, LPTSTR firstword, LPTSTR param)
225 {
226         HANDLE hFile;
227
228         hFile = CreateFile (fullname, GENERIC_READ, FILE_SHARE_READ, NULL,
229                                                 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL |
230                                                 FILE_FLAG_SEQUENTIAL_SCAN, NULL);
231
232 #ifdef _DEBUG
233         DebugPrintf (_T("Batch: (\'%s\', \'%s\', \'%s\')  hFile = %x\n"),
234                                  fullname, firstword, param, hFile);
235 #endif
236
237         if (hFile == INVALID_HANDLE_VALUE)
238         {
239                 ConErrPrintf (_T("Error opening batch file\n"));
240                 return FALSE;
241         }
242
243         /* Kill any and all FOR contexts */
244         while (bc && bc->forvar)
245                 ExitBatch (NULL);
246
247         if (bc == NULL)
248         {
249                 /* No curent batch file, create a new context */
250                 LPBATCH_CONTEXT n = (LPBATCH_CONTEXT)malloc (sizeof(BATCH_CONTEXT));
251
252                 if (n == NULL)
253                 {
254                         error_out_of_memory ();
255                         return FALSE;
256                 }
257
258                 n->prev = bc;
259                 bc = n;
260         }
261         else if (bc->hBatchFile != INVALID_HANDLE_VALUE)
262         {
263                 /* Then we are transferring to another batch */
264                 CloseHandle (bc->hBatchFile);
265                 bc->hBatchFile = INVALID_HANDLE_VALUE;
266                 free (bc->params);
267         }
268
269         bc->hBatchFile = hFile;
270         bc->bEcho = bEcho; /* Preserve echo across batch calls */
271         bc->shiftlevel = 0;
272
273         bc->ffind = NULL;
274         bc->forvar = _T('\0');
275         bc->forproto = NULL;
276         bc->params = BatchParams (firstword, param);
277
278 #ifdef _DEBUG
279         DebugPrintf (_T("Batch: returns TRUE\n"));
280 #endif
281
282         return TRUE;
283 }
284
285
286 /*
287  * Read and return the next executable line form the current batch file
288  *
289  * If no batch file is current or no further executable lines are found
290  * return NULL.
291  *
292  * Here we also look out for FOR bcontext structures which trigger the
293  * FOR expansion code.
294  *
295  * Set eflag to 0 if line is not to be echoed else 1
296  */
297
298 LPTSTR ReadBatchLine (LPBOOL bLocalEcho)
299 {
300         LPTSTR first;
301         LPTSTR ip;
302
303         /* No batch */
304         if (bc == NULL)
305                 return NULL;
306
307 #ifdef _DEBUG
308         DebugPrintf (_T("ReadBatchLine ()\n"));
309 #endif
310
311         while (1)
312         {
313                 /* User halt */
314                 if (CheckCtrlBreak (BREAK_BATCHFILE))
315                 {
316                         while (bc)
317                                 ExitBatch (NULL);
318                         return NULL;
319                 }
320
321                 /* No batch */
322                 if (bc == NULL)
323                         return NULL;
324
325                 /* If its a FOR context... */
326                 if (bc->forvar)
327                 {
328                         LPTSTR sp = bc->forproto; /* pointer to prototype command */
329                         LPTSTR dp = textline;     /* Place to expand protoype */
330                         LPTSTR fv = FindArg (0);  /* Next list element */
331
332                         /* End of list so... */
333                         if ((fv == NULL) || (*fv == _T('\0')))
334                         {
335                                 /* just exit this context */
336                                 ExitBatch (NULL);
337                                 continue;
338                         }
339
340                         if (_tcscspn (fv, _T("?*")) == _tcslen (fv))
341                         {
342                                 /* element is wild file */
343                                 bc->shiftlevel++;       /* No use it and shift list */
344                         }
345                         else
346                         {
347                                 /* Wild file spec, find first (or next) file name */
348                                 if (bc->ffind)
349                                 {
350                                         /* First already done so do next */
351
352                                         fv = FindNextFile (bc->hFind, bc->ffind) ? bc->ffind->cFileName : NULL;
353                                 }
354                                 else
355                                 {
356                                         /*  For first find, allocate a find first block */
357                                         if ((bc->ffind = (LPWIN32_FIND_DATA)malloc (sizeof (WIN32_FIND_DATA))) == NULL)
358                                         {
359                                                 error_out_of_memory();
360                                                 return NULL;
361                                         }
362
363                                         bc->hFind = FindFirstFile (fv, bc->ffind);
364
365                                         fv = !(bc->hFind==INVALID_HANDLE_VALUE) ? bc->ffind->cFileName : NULL;
366                                 }
367
368                                 if (fv == NULL)
369                                 {
370                                         /* Null indicates no more files.. */
371                                         free (bc->ffind);      /* free the buffer */
372                                         bc->ffind = NULL;
373                                         bc->shiftlevel++;     /* On to next list element */
374                                         continue;
375                                 }
376                         }
377
378                         /* At this point, fv points to parameter string */
379                         while (*sp)
380                         {
381                                 if ((*sp == _T('%')) && (*(sp + 1) == bc->forvar))
382                                 {
383                                         /* replace % var */
384                                         dp = stpcpy (dp, fv);
385                                         sp += 2;
386                                 }
387                                 else
388                                 {
389                                         /* Else just copy */
390                                         *dp++ = *sp++;
391                                 }
392                         }
393
394                         *dp = _T('\0');
395
396                         *bLocalEcho = bEcho;
397
398                         return textline;
399                 }
400
401                 if (!FileGetString (bc->hBatchFile, textline, sizeof (textline)))
402                 {
403 #ifdef _DEBUG
404                         DebugPrintf (_T("ReadBatchLine(): Reached EOF!\n"));
405 #endif
406                         /* End of file.... */
407                         ExitBatch (NULL);
408
409                         if (bc == NULL)
410                                 return NULL;
411
412                         continue;
413                 }
414
415 #ifdef _DEBUG
416                 DebugPrintf (_T("ReadBatchLine(): textline: \'%s\'\n"), textline);
417 #endif
418
419                 /* Strip leading spaces and trailing space/control chars */
420                 for (first = textline; _istspace (*first); first++)
421                         ;
422
423                 for (ip = first + _tcslen (first) - 1; _istspace (*ip) || _istcntrl (*ip); ip--)
424                         ;
425
426                 *++ip = _T('\0');
427
428                 /* ignore labels and empty lines */
429                 if (*first == _T(':') || *first == 0)
430                         continue;
431
432                 if (*first == _T('@'))
433                 {
434                         /* don't echo this line */
435                         do
436                                 first++;
437                         while (_istspace (*first));
438
439                         *bLocalEcho = 0;
440                 }
441                 else
442                         *bLocalEcho = bEcho;
443
444                 break;
445         }
446
447         return first;
448 }
449
450 /* EOF */