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