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