Updated years in version info.
[reactos.git] / rosapps / cmd / batch.c
1 /* $Id: batch.c,v 1.3 1999/10/03 22:20:32 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
56 #include "config.h"
57
58 #include <windows.h>
59 #include <tchar.h>
60 #include <string.h>
61 #include <stdlib.h>
62 #include <ctype.h>
63
64 #include "cmd.h"
65 #include "batch.h"
66
67
68 /* The stack of current batch contexts.
69 * NULL when no batch is active
70 */
71 LPBATCH_CONTEXT bc = NULL;
72
73 BOOL bEcho = TRUE; /* The echo flag */
74
75
76
77 /* Buffer for reading Batch file lines */
78 TCHAR textline[BATCH_BUFFSIZE];
79
80
81 /*
82 * Returns a pointer to the n'th parameter of the current batch file.
83 * If no such parameter exists returns pointer to empty string.
84 * If no batch file is current, returns NULL
85 *
86 */
87
88 LPTSTR FindArg (INT n)
89 {
90 LPTSTR pp;
91
92 #ifdef _DEBUG
93 DebugPrintf ("FindArg: (%d)\n", n);
94 #endif
95
96 if (bc == NULL)
97 return NULL;
98
99 n += bc->shiftlevel;
100 pp = bc->params;
101
102 /* Step up the strings till we reach the end */
103 /* or the one we want */
104 while (*pp && n--)
105 pp += _tcslen (pp) + 1;
106
107 return pp;
108 }
109
110
111 /*
112 * Batch_params builds a parameter list in newlay allocated memory.
113 * The parameters consist of null terminated strings with a final
114 * NULL character signalling the end of the parameters.
115 *
116 */
117
118 LPTSTR BatchParams (LPTSTR s1, LPTSTR s2)
119 {
120 LPTSTR dp = (LPTSTR)malloc ((_tcslen(s1) + _tcslen(s2) + 3) * sizeof (TCHAR));
121
122 /* JPP 20-Jul-1998 added error checking */
123 if (dp == NULL)
124 {
125 error_out_of_memory();
126 return NULL;
127 }
128
129 if (s1 && *s1)
130 {
131 s1 = stpcpy (dp, s1);
132 *s1++ = _T('\0');
133 }
134 else
135 s1 = dp;
136
137 while (*s2)
138 {
139 if (_istspace (*s2) || _tcschr (_T(",;"), *s2))
140 {
141 *s1++ = _T('\0');
142 s2++;
143 while (*s2 && _tcschr (_T(" ,;"), *s2))
144 s2++;
145 continue;
146 }
147
148 if ((*s2 == _T('"')) || (*s2 == _T('\'')))
149 {
150 TCHAR st = *s2;
151
152 do
153 *s1++ = *s2++;
154 while (*s2 && (*s2 != st));
155 }
156
157 *s1++ = *s2++;
158 }
159
160 *s1++ = _T('\0');
161 *s1 = _T('\0');
162
163 return dp;
164 }
165
166
167 /*
168 * If a batch file is current, exits it, freeing the context block and
169 * chaining back to the previous one.
170 *
171 * If no new batch context is found, sets ECHO back ON.
172 *
173 * If the parameter is non-null or not empty, it is printed as an exit
174 * message
175 */
176
177 VOID ExitBatch (LPTSTR msg)
178 {
179 #ifdef _DEBUG
180 DebugPrintf ("ExitBatch: (\'%s\')\n", msg);
181 #endif
182
183 if (bc != NULL)
184 {
185 LPBATCH_CONTEXT t = bc;
186
187 if (bc->hBatchFile)
188 {
189 CloseHandle (bc->hBatchFile);
190 bc->hBatchFile = INVALID_HANDLE_VALUE;
191 }
192
193 if (bc->params)
194 free(bc->params);
195
196 if (bc->forproto)
197 free(bc->forproto);
198
199 if (bc->ffind)
200 free(bc->ffind);
201
202 /* Preserve echo state across batch calls */
203 bEcho = bc->bEcho;
204
205 bc = bc->prev;
206 free(t);
207 }
208
209 if (msg && *msg)
210 ConOutPrintf ("%s\n", msg);
211 }
212
213
214 /*
215 * Start batch file execution
216 *
217 * The firstword parameter is the full filename of the batch file.
218 *
219 */
220
221 BOOL Batch (LPTSTR fullname, LPTSTR firstword, LPTSTR param)
222 {
223 HANDLE hFile;
224
225 hFile = CreateFile (fullname, GENERIC_READ, FILE_SHARE_READ, NULL,
226 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL |
227 FILE_FLAG_SEQUENTIAL_SCAN, NULL);
228
229 #ifdef _DEBUG
230 DebugPrintf ("Batch: (\'%s\', \'%s\', \'%s\') hFile = %x\n",
231 fullname, firstword, param, hFile);
232 #endif
233
234 if (hFile == INVALID_HANDLE_VALUE)
235 {
236 ConErrPrintf (_T("Error opening batch file\n"));
237 return FALSE;
238 }
239
240 /* Kill any and all FOR contexts */
241 while (bc && bc->forvar)
242 ExitBatch (NULL);
243
244 if (bc == NULL)
245 {
246 /* No curent batch file, create a new context */
247 LPBATCH_CONTEXT n = (LPBATCH_CONTEXT)malloc (sizeof(BATCH_CONTEXT));
248
249 if (n == NULL)
250 {
251 error_out_of_memory ();
252 return FALSE;
253 }
254
255 n->prev = bc;
256 bc = n;
257 }
258 else if (bc->hBatchFile != INVALID_HANDLE_VALUE)
259 {
260 /* Then we are transferring to another batch */
261 CloseHandle (bc->hBatchFile);
262 bc->hBatchFile = INVALID_HANDLE_VALUE;
263 free (bc->params);
264 }
265
266 bc->hBatchFile = hFile;
267 bc->bEcho = bEcho; /* Preserve echo across batch calls */
268 bc->shiftlevel = 0;
269
270 bc->ffind = NULL;
271 bc->forvar = _T('\0');
272 bc->forproto = NULL;
273 bc->params = BatchParams (firstword, param);
274
275 #ifdef _DEBUG
276 DebugPrintf ("Batch: returns TRUE\n");
277 #endif
278
279 return TRUE;
280 }
281
282
283 /*
284 * Read and return the next executable line form the current batch file
285 *
286 * If no batch file is current or no further executable lines are found
287 * return NULL.
288 *
289 * Here we also look out for FOR bcontext structures which trigger the
290 * FOR expansion code.
291 *
292 * Set eflag to 0 if line is not to be echoed else 1
293 */
294
295 LPTSTR ReadBatchLine (LPBOOL bLocalEcho)
296 {
297 HANDLE hFind = INVALID_HANDLE_VALUE;
298 LPTSTR first;
299 LPTSTR ip;
300
301 /* No batch */
302 if (bc == NULL)
303 return NULL;
304
305 #ifdef _DEBUG
306 DebugPrintf ("ReadBatchLine ()\n");
307 #endif
308
309 while (1)
310 {
311 /* User halt */
312 if (CheckCtrlBreak (BREAK_BATCHFILE))
313 {
314 while (bc)
315 ExitBatch (NULL);
316 return NULL;
317 }
318
319 /* No batch */
320 if (bc == NULL)
321 return NULL;
322
323 /* If its a FOR context... */
324 if (bc->forvar)
325 {
326 LPTSTR sp = bc->forproto; /* pointer to prototype command */
327 LPTSTR dp = textline; /* Place to expand protoype */
328 LPTSTR fv = FindArg (0); /* Next list element */
329
330 /* End of list so... */
331 if ((fv == NULL) || (*fv == _T('\0')))
332 {
333 /* just exit this context */
334 ExitBatch (NULL);
335 continue;
336 }
337
338 if (_tcscspn (fv, _T("?*")) == _tcslen (fv))
339 {
340 /* element is wild file */
341 bc->shiftlevel++; /* No use it and shift list */
342 }
343 else
344 {
345 /* Wild file spec, find first (or next) file name */
346 if (bc->ffind)
347 {
348 /* First already done so do next */
349 fv = FindNextFile (hFind, bc->ffind) ? bc->ffind->cFileName : NULL;
350 }
351 else
352 {
353 /* For first find, allocate a find first block */
354 if ((bc->ffind = (LPWIN32_FIND_DATA)malloc (sizeof (WIN32_FIND_DATA))) == NULL)
355 {
356 error_out_of_memory();
357 return NULL;
358 }
359
360 hFind = FindFirstFile (fv, bc->ffind);
361 fv = !(hFind==INVALID_HANDLE_VALUE) ? bc->ffind->cFileName : NULL;
362 }
363
364 if (fv == NULL)
365 {
366 /* Null indicates no more files.. */
367 free (bc->ffind); /* free the buffer */
368 bc->ffind = NULL;
369 bc->shiftlevel++; /* On to next list element */
370 continue;
371 }
372 }
373
374 /* At this point, fv points to parameter string */
375 while (*sp)
376 {
377 if ((*sp == _T('%')) && (*(sp + 1) == bc->forvar))
378 {
379 /* replace % var */
380 dp = stpcpy (dp, fv);
381 sp += 2;
382 }
383 else
384 {
385 /* Else just copy */
386 *dp++ = *sp++;
387 }
388 }
389
390 *dp = _T('\0');
391
392 *bLocalEcho = bEcho;
393
394 return textline;
395 }
396
397 if (!FileGetString (bc->hBatchFile, textline, sizeof (textline)))
398 {
399 #ifdef _DEBUG
400 DebugPrintf (_T("ReadBatchLine(): Reached EOF!\n"));
401 #endif
402 /* End of file.... */
403 ExitBatch (NULL);
404
405 if (bc == NULL)
406 return NULL;
407
408 continue;
409 }
410
411 #ifdef _DEBUG
412 DebugPrintf (_T("ReadBatchLine(): textline: \'%s\'\n"), textline);
413 #endif
414
415 /* Strip leading spaces and trailing space/control chars */
416 for (first = textline; _istspace (*first); first++)
417 ;
418
419 for (ip = first + _tcslen (first) - 1; _istspace (*ip) || _istcntrl (*ip); ip--)
420 ;
421
422 *++ip = _T('\0');
423
424 /* ignore labels and empty lines */
425 if (*first == _T(':') || *first == 0)
426 continue;
427
428 if (*first == _T('@'))
429 {
430 /* don't echo this line */
431 do
432 first++;
433 while (_istspace (*first));
434
435 *bLocalEcho = 0;
436 }
437 else
438 *bLocalEcho = bEcho;
439
440 break;
441 }
442
443 return first;
444 }
445
446 /* EOF */