[CMD]: Continue refactoring to lay out the way to using the CONUTILS library in CMD...
[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 * Separated 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 cmd_alloc 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
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 hardcoded strings in En.rc
59 */
60
61 #include "precomp.h"
62
63 /* The stack of current batch contexts.
64 * NULL when no batch is active
65 */
66 LPBATCH_CONTEXT bc = NULL;
67
68 BOOL bEcho = TRUE; /* The echo flag */
69
70
71
72 /* Buffer for reading Batch file lines */
73 TCHAR textline[BATCH_BUFFSIZE];
74
75
76 /*
77 * Returns a pointer to the n'th parameter of the current batch file.
78 * If no such parameter exists returns pointer to empty string.
79 * If no batch file is current, returns NULL
80 *
81 */
82
83 LPTSTR FindArg(TCHAR Char, BOOL *IsParam0)
84 {
85 LPTSTR pp;
86 INT n = Char - _T('0');
87
88 TRACE ("FindArg: (%d)\n", n);
89
90 if (n < 0 || n > 9)
91 return NULL;
92
93 n = bc->shiftlevel[n];
94 *IsParam0 = (n == 0);
95 pp = bc->params;
96
97 /* Step up the strings till we reach the end */
98 /* or the one we want */
99 while (*pp && n--)
100 pp += _tcslen (pp) + 1;
101
102 return pp;
103 }
104
105
106 /*
107 * Batch_params builds a parameter list in newly allocated memory.
108 * The parameters consist of null terminated strings with a final
109 * NULL character signalling the end of the parameters.
110 *
111 */
112
113 LPTSTR BatchParams (LPTSTR s1, LPTSTR s2)
114 {
115 LPTSTR dp = (LPTSTR)cmd_alloc ((_tcslen(s1) + _tcslen(s2) + 3) * sizeof (TCHAR));
116
117 /* JPP 20-Jul-1998 added error checking */
118 if (dp == NULL)
119 {
120 error_out_of_memory();
121 return NULL;
122 }
123
124 if (s1 && *s1)
125 {
126 s1 = _stpcpy (dp, s1);
127 *s1++ = _T('\0');
128 }
129 else
130 s1 = dp;
131
132 while (*s2)
133 {
134 BOOL inquotes = FALSE;
135
136 /* Find next parameter */
137 while (_istspace(*s2) || (*s2 && _tcschr(_T(",;="), *s2)))
138 s2++;
139 if (!*s2)
140 break;
141
142 /* Copy it */
143 do
144 {
145 if (!inquotes && (_istspace(*s2) || _tcschr(_T(",;="), *s2)))
146 break;
147 inquotes ^= (*s2 == _T('"'));
148 *s1++ = *s2++;
149 } while (*s2);
150 *s1++ = _T('\0');
151 }
152
153 *s1 = _T('\0');
154
155 return dp;
156 }
157
158 /*
159 * free the allocated memory of a batch file
160 */
161 VOID ClearBatch()
162 {
163 TRACE ("ClearBatch mem = %08x free = %d\n", bc->mem, bc->memfree);
164
165 if (bc->mem && bc->memfree)
166 cmd_free(bc->mem);
167
168 if (bc->raw_params)
169 cmd_free(bc->raw_params);
170
171 if (bc->params)
172 cmd_free(bc->params);
173 }
174
175 /*
176 * If a batch file is current, exits it, freeing the context block and
177 * chaining back to the previous one.
178 *
179 * If no new batch context is found, sets ECHO back ON.
180 *
181 * If the parameter is non-null or not empty, it is printed as an exit
182 * message
183 */
184
185 VOID ExitBatch()
186 {
187 ClearBatch();
188
189 TRACE ("ExitBatch\n");
190
191 UndoRedirection(bc->RedirList, NULL);
192 FreeRedirection(bc->RedirList);
193
194 /* Preserve echo state across batch calls */
195 bEcho = bc->bEcho;
196
197 while (bc->setlocal)
198 cmd_endlocal(_T(""));
199
200 bc = bc->prev;
201 }
202
203 /*
204 * Load batch file into memory
205 *
206 */
207 void BatchFile2Mem(HANDLE hBatchFile)
208 {
209 TRACE ("BatchFile2Mem ()\n");
210
211 bc->memsize = GetFileSize(hBatchFile, NULL);
212 bc->mem = (char *)cmd_alloc(bc->memsize+1); /* 1 extra for '\0' */
213
214 /* if memory is available, read it in and close the file */
215 if (bc->mem != NULL)
216 {
217 TRACE ("BatchFile2Mem memory %08x - %08x\n",bc->mem,bc->memsize);
218 SetFilePointer (hBatchFile, 0, NULL, FILE_BEGIN);
219 ReadFile(hBatchFile, (LPVOID)bc->mem, bc->memsize, &bc->memsize, NULL);
220 bc->mem[bc->memsize]='\0'; /* end this, so you can dump it as a string */
221 bc->memfree=TRUE; /* this one needs to be freed */
222 }
223 else
224 {
225 bc->memsize=0; /* this will prevent mem being accessed */
226 bc->memfree=FALSE;
227 }
228 bc->mempos = 0; /* set position to the start */
229 }
230
231 /*
232 * Start batch file execution
233 *
234 * The firstword parameter is the full filename of the batch file.
235 *
236 */
237 INT Batch (LPTSTR fullname, LPTSTR firstword, LPTSTR param, PARSED_COMMAND *Cmd)
238 {
239 BATCH_CONTEXT new;
240 LPFOR_CONTEXT saved_fc;
241 INT i;
242 INT ret = 0;
243 BOOL same_fn = FALSE;
244
245 HANDLE hFile = 0;
246 SetLastError(0);
247 if (bc && bc->mem)
248 {
249 TCHAR fpname[MAX_PATH];
250 GetFullPathName(fullname, sizeof(fpname) / sizeof(TCHAR), fpname, NULL);
251 if (_tcsicmp(bc->BatchFilePath,fpname)==0)
252 same_fn=TRUE;
253 }
254 TRACE ("Batch: (\'%s\', \'%s\', \'%s\') same_fn = %d\n",
255 debugstr_aw(fullname), debugstr_aw(firstword), debugstr_aw(param), same_fn);
256
257 if (!same_fn)
258 {
259 hFile = CreateFile(fullname, GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, NULL,
260 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL |
261 FILE_FLAG_SEQUENTIAL_SCAN, NULL);
262
263 if (hFile == INVALID_HANDLE_VALUE)
264 {
265 ConErrResPuts(STRING_BATCH_ERROR);
266 return 1;
267 }
268 }
269
270 if (bc != NULL && Cmd == bc->current)
271 {
272 /* Then we are transferring to another batch */
273 ClearBatch();
274 AddBatchRedirection(&Cmd->Redirections);
275 }
276 else
277 {
278 struct _SETLOCAL *setlocal = NULL;
279
280 if (Cmd == NULL)
281 {
282 /* This is a CALL. CALL will set errorlevel to our return value, so
283 * in order to keep the value of errorlevel unchanged in the case
284 * of calling an empty batch file, we must return that same value. */
285 ret = nErrorLevel;
286 }
287 else if (bc)
288 {
289 /* If a batch file runs another batch file as part of a compound command
290 * (e.g. "x.bat & somethingelse") then the first file gets terminated. */
291
292 /* Get its SETLOCAL stack so it can be migrated to the new context */
293 setlocal = bc->setlocal;
294 bc->setlocal = NULL;
295 ExitBatch();
296 }
297
298 /* Create a new context. This function will not
299 * return until this context has been exited */
300 new.prev = bc;
301 /* copy some fields in the new structure if it is the same file */
302 if (same_fn) {
303 new.mem = bc->mem;
304 new.memsize = bc->memsize;
305 new.mempos = 0;
306 new.memfree = FALSE; /* don't free this, being used before this */
307 }
308 bc = &new;
309 bc->RedirList = NULL;
310 bc->setlocal = setlocal;
311 }
312
313 GetFullPathName(fullname, sizeof(bc->BatchFilePath) / sizeof(TCHAR), bc->BatchFilePath, NULL);
314 /* if a new batch file, load it into memory and close the file */
315 if (!same_fn)
316 {
317 BatchFile2Mem(hFile);
318 CloseHandle(hFile);
319 }
320
321 bc->mempos = 0; /* goto begin of batch file */
322 bc->bEcho = bEcho; /* Preserve echo across batch calls */
323 for (i = 0; i < 10; i++)
324 bc->shiftlevel[i] = i;
325
326 bc->params = BatchParams (firstword, param);
327 //
328 // Allocate enough memory to hold the params and copy them over without modifications
329 //
330 bc->raw_params = cmd_dup(param);
331 if (bc->raw_params == NULL)
332 {
333 error_out_of_memory();
334 return 1;
335 }
336
337 /* Check if this is a "CALL :label" */
338 if (*firstword == _T(':'))
339 cmd_goto(firstword);
340
341 /* If we are calling from inside a FOR, hide the FOR variables */
342 saved_fc = fc;
343 fc = NULL;
344
345 /* If we have created a new context, don't return
346 * until this batch file has completed. */
347 while (bc == &new && !bExit)
348 {
349 Cmd = ParseCommand(NULL);
350 if (!Cmd)
351 continue;
352
353 /* JPP 19980807 */
354 /* Echo batch file line */
355 if (bEcho && !bDisableBatchEcho && Cmd->Type != C_QUIET)
356 {
357 if (!bIgnoreEcho)
358 ConOutChar(_T('\n'));
359 PrintPrompt();
360 EchoCommand(Cmd);
361 ConOutChar(_T('\n'));
362 }
363
364 bc->current = Cmd;
365 ret = ExecuteCommand(Cmd);
366 FreeCommand(Cmd);
367 }
368
369 TRACE ("Batch: returns TRUE\n");
370
371 fc = saved_fc;
372 return ret;
373 }
374
375 VOID AddBatchRedirection(REDIRECTION **RedirList)
376 {
377 REDIRECTION **ListEnd;
378
379 /* Prepend the list to the batch context's list */
380 ListEnd = RedirList;
381 while (*ListEnd)
382 ListEnd = &(*ListEnd)->Next;
383 *ListEnd = bc->RedirList;
384 bc->RedirList = *RedirList;
385
386 /* Null out the pointer so that the list will not be cleared prematurely.
387 * These redirections should persist until the batch file exits. */
388 *RedirList = NULL;
389 }
390
391 /*
392 * Read a single line from the batch file from the current batch/memory position.
393 * Almost a copy of FileGetString with same UNICODE handling
394 */
395 BOOL BatchGetString (LPTSTR lpBuffer, INT nBufferLength)
396 {
397 LPSTR lpString;
398 INT len = 0;
399 #ifdef _UNICODE
400 lpString = cmd_alloc(nBufferLength);
401 #else
402 lpString = lpBuffer;
403 #endif
404 /* read all chars from memory until a '\n' is encountered */
405 if (bc->mem)
406 {
407 for (; (bc->mempos < bc->memsize && len < (nBufferLength-1)); len++)
408 {
409 lpString[len] = bc->mem[bc->mempos++];
410 if (lpString[len] == '\n' )
411 {
412 len++;
413 break;
414 }
415 }
416 }
417
418 if (!len)
419 {
420 #ifdef _UNICODE
421 cmd_free(lpString);
422 #endif
423 return FALSE;
424 }
425
426 lpString[len++] = '\0';
427 #ifdef _UNICODE
428 MultiByteToWideChar(OutputCodePage, 0, lpString, -1, lpBuffer, len);
429 cmd_free(lpString);
430 #endif
431 return TRUE;
432 }
433
434 /*
435 * Read and return the next executable line form the current batch file
436 *
437 * If no batch file is current or no further executable lines are found
438 * return NULL.
439 *
440 * Set eflag to 0 if line is not to be echoed else 1
441 */
442 LPTSTR ReadBatchLine ()
443 {
444 TRACE ("ReadBatchLine ()\n");
445
446 /* User halt */
447 if (CheckCtrlBreak (BREAK_BATCHFILE))
448 {
449 while (bc)
450 ExitBatch();
451 return NULL;
452 }
453
454 if (!BatchGetString (textline, sizeof (textline) / sizeof (textline[0]) - 1))
455 {
456 TRACE ("ReadBatchLine(): Reached EOF!\n");
457 /* End of file.... */
458 ExitBatch();
459 return NULL;
460 }
461
462 TRACE ("ReadBatchLine(): textline: \'%s\'\n", debugstr_aw(textline));
463
464 if (textline[_tcslen(textline) - 1] != _T('\n'))
465 _tcscat(textline, _T("\n"));
466
467 return textline;
468 }
469
470 /* EOF */