[CSCRIPT][WSCRIPT] Sync with Wine Staging 3.17. CORE-15127
[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 * 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 WARN("Cannot allocate memory for dp!\n");
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(VOID)
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(VOID)
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 INT Batch(LPTSTR fullname, LPTSTR firstword, LPTSTR param, PARSED_COMMAND *Cmd)
239 {
240 BATCH_CONTEXT new;
241 LPFOR_CONTEXT saved_fc;
242 INT i;
243 INT ret = 0;
244 BOOL same_fn = FALSE;
245
246 HANDLE hFile = 0;
247 SetLastError(0);
248 if (bc && bc->mem)
249 {
250 TCHAR fpname[MAX_PATH];
251 GetFullPathName(fullname, sizeof(fpname) / sizeof(TCHAR), fpname, NULL);
252 if (_tcsicmp(bc->BatchFilePath,fpname)==0)
253 same_fn=TRUE;
254 }
255 TRACE ("Batch: (\'%s\', \'%s\', \'%s\') same_fn = %d\n",
256 debugstr_aw(fullname), debugstr_aw(firstword), debugstr_aw(param), same_fn);
257
258 if (!same_fn)
259 {
260 hFile = CreateFile(fullname, GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, NULL,
261 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL |
262 FILE_FLAG_SEQUENTIAL_SCAN, NULL);
263
264 if (hFile == INVALID_HANDLE_VALUE)
265 {
266 ConErrResPuts(STRING_BATCH_ERROR);
267 return 1;
268 }
269 }
270
271 if (bc != NULL && Cmd == bc->current)
272 {
273 /* Then we are transferring to another batch */
274 ClearBatch();
275 AddBatchRedirection(&Cmd->Redirections);
276 }
277 else
278 {
279 struct _SETLOCAL *setlocal = NULL;
280
281 if (Cmd == NULL)
282 {
283 /* This is a CALL. CALL will set errorlevel to our return value, so
284 * in order to keep the value of errorlevel unchanged in the case
285 * of calling an empty batch file, we must return that same value. */
286 ret = nErrorLevel;
287 }
288 else if (bc)
289 {
290 /* If a batch file runs another batch file as part of a compound command
291 * (e.g. "x.bat & somethingelse") then the first file gets terminated. */
292
293 /* Get its SETLOCAL stack so it can be migrated to the new context */
294 setlocal = bc->setlocal;
295 bc->setlocal = NULL;
296 ExitBatch();
297 }
298
299 /* Create a new context. This function will not
300 * return until this context has been exited */
301 new.prev = bc;
302 /* copy some fields in the new structure if it is the same file */
303 if (same_fn)
304 {
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 /* Always return the current errorlevel */
372 ret = nErrorLevel;
373
374 TRACE ("Batch: returns TRUE\n");
375
376 fc = saved_fc;
377 return ret;
378 }
379
380 VOID AddBatchRedirection(REDIRECTION **RedirList)
381 {
382 REDIRECTION **ListEnd;
383
384 /* Prepend the list to the batch context's list */
385 ListEnd = RedirList;
386 while (*ListEnd)
387 ListEnd = &(*ListEnd)->Next;
388 *ListEnd = bc->RedirList;
389 bc->RedirList = *RedirList;
390
391 /* Null out the pointer so that the list will not be cleared prematurely.
392 * These redirections should persist until the batch file exits. */
393 *RedirList = NULL;
394 }
395
396 /*
397 * Read a single line from the batch file from the current batch/memory position.
398 * Almost a copy of FileGetString with same UNICODE handling
399 */
400 BOOL BatchGetString(LPTSTR lpBuffer, INT nBufferLength)
401 {
402 LPSTR lpString;
403 INT len = 0;
404 #ifdef _UNICODE
405 lpString = cmd_alloc(nBufferLength);
406 if (!lpString)
407 {
408 WARN("Cannot allocate memory for lpString\n");
409 error_out_of_memory();
410 return FALSE;
411 }
412 #else
413 lpString = lpBuffer;
414 #endif
415 /* read all chars from memory until a '\n' is encountered */
416 if (bc->mem)
417 {
418 for (; (bc->mempos < bc->memsize && len < (nBufferLength-1)); len++)
419 {
420 lpString[len] = bc->mem[bc->mempos++];
421 if (lpString[len] == '\n' )
422 {
423 len++;
424 break;
425 }
426 }
427 }
428
429 if (!len)
430 {
431 #ifdef _UNICODE
432 cmd_free(lpString);
433 #endif
434 return FALSE;
435 }
436
437 lpString[len++] = '\0';
438 #ifdef _UNICODE
439 MultiByteToWideChar(OutputCodePage, 0, lpString, -1, lpBuffer, len);
440 cmd_free(lpString);
441 #endif
442 return TRUE;
443 }
444
445 /*
446 * Read and return the next executable line form the current batch file
447 *
448 * If no batch file is current or no further executable lines are found
449 * return NULL.
450 *
451 * Set eflag to 0 if line is not to be echoed else 1
452 */
453 LPTSTR ReadBatchLine(VOID)
454 {
455 TRACE ("ReadBatchLine ()\n");
456
457 /* User halt */
458 if (CheckCtrlBreak (BREAK_BATCHFILE))
459 {
460 while (bc)
461 ExitBatch();
462 return NULL;
463 }
464
465 if (!BatchGetString (textline, sizeof (textline) / sizeof (textline[0]) - 1))
466 {
467 TRACE ("ReadBatchLine(): Reached EOF!\n");
468 /* End of file.... */
469 ExitBatch();
470 return NULL;
471 }
472
473 TRACE ("ReadBatchLine(): textline: \'%s\'\n", debugstr_aw(textline));
474
475 if (textline[_tcslen(textline) - 1] != _T('\n'))
476 _tcscat(textline, _T("\n"));
477
478 return textline;
479 }
480
481 /* EOF */