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