Various improvements by Carl Nettelblad.
[reactos.git] / rosapps / cmd / batch.c
1 /* $Id: batch.c,v 1.4 2001/02/28 22:33:23 ekohl Exp $
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 ("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 ("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 ("%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 ("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 ("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 ("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 */