[FLOPPY] Don't wait forever while trying to determine media type.
[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 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 {
304 new.mem = bc->mem;
305 new.memsize = bc->memsize;
306 new.mempos = 0;
307 new.memfree = FALSE; /* don't free this, being used before this */
308 }
309 bc = &new;
310 bc->RedirList = NULL;
311 bc->setlocal = setlocal;
312 }
313
314 GetFullPathName(fullname, sizeof(bc->BatchFilePath) / sizeof(TCHAR), bc->BatchFilePath, NULL);
315 /* if a new batch file, load it into memory and close the file */
316 if (!same_fn)
317 {
318 BatchFile2Mem(hFile);
319 CloseHandle(hFile);
320 }
321
322 bc->mempos = 0; /* goto begin of batch file */
323 bc->bEcho = bEcho; /* Preserve echo across batch calls */
324 for (i = 0; i < 10; i++)
325 bc->shiftlevel[i] = i;
326
327 bc->params = BatchParams (firstword, param);
328 //
329 // Allocate enough memory to hold the params and copy them over without modifications
330 //
331 bc->raw_params = cmd_dup(param);
332 if (bc->raw_params == NULL)
333 {
334 error_out_of_memory();
335 return 1;
336 }
337
338 /* Check if this is a "CALL :label" */
339 if (*firstword == _T(':'))
340 cmd_goto(firstword);
341
342 /* If we are calling from inside a FOR, hide the FOR variables */
343 saved_fc = fc;
344 fc = NULL;
345
346 /* If we have created a new context, don't return
347 * until this batch file has completed. */
348 while (bc == &new && !bExit)
349 {
350 Cmd = ParseCommand(NULL);
351 if (!Cmd)
352 continue;
353
354 /* JPP 19980807 */
355 /* Echo batch file line */
356 if (bEcho && !bDisableBatchEcho && Cmd->Type != C_QUIET)
357 {
358 if (!bIgnoreEcho)
359 ConOutChar(_T('\n'));
360 PrintPrompt();
361 EchoCommand(Cmd);
362 ConOutChar(_T('\n'));
363 }
364
365 bc->current = Cmd;
366 ret = ExecuteCommand(Cmd);
367 FreeCommand(Cmd);
368 }
369
370 /* Always return the current errorlevel */
371 ret = nErrorLevel;
372
373 TRACE ("Batch: returns TRUE\n");
374
375 fc = saved_fc;
376 return ret;
377 }
378
379 VOID AddBatchRedirection(REDIRECTION **RedirList)
380 {
381 REDIRECTION **ListEnd;
382
383 /* Prepend the list to the batch context's list */
384 ListEnd = RedirList;
385 while (*ListEnd)
386 ListEnd = &(*ListEnd)->Next;
387 *ListEnd = bc->RedirList;
388 bc->RedirList = *RedirList;
389
390 /* Null out the pointer so that the list will not be cleared prematurely.
391 * These redirections should persist until the batch file exits. */
392 *RedirList = NULL;
393 }
394
395 /*
396 * Read a single line from the batch file from the current batch/memory position.
397 * Almost a copy of FileGetString with same UNICODE handling
398 */
399 BOOL BatchGetString (LPTSTR lpBuffer, INT nBufferLength)
400 {
401 LPSTR lpString;
402 INT len = 0;
403 #ifdef _UNICODE
404 lpString = cmd_alloc(nBufferLength);
405 #else
406 lpString = lpBuffer;
407 #endif
408 /* read all chars from memory until a '\n' is encountered */
409 if (bc->mem)
410 {
411 for (; (bc->mempos < bc->memsize && len < (nBufferLength-1)); len++)
412 {
413 lpString[len] = bc->mem[bc->mempos++];
414 if (lpString[len] == '\n' )
415 {
416 len++;
417 break;
418 }
419 }
420 }
421
422 if (!len)
423 {
424 #ifdef _UNICODE
425 cmd_free(lpString);
426 #endif
427 return FALSE;
428 }
429
430 lpString[len++] = '\0';
431 #ifdef _UNICODE
432 MultiByteToWideChar(OutputCodePage, 0, lpString, -1, lpBuffer, len);
433 cmd_free(lpString);
434 #endif
435 return TRUE;
436 }
437
438 /*
439 * Read and return the next executable line form the current batch file
440 *
441 * If no batch file is current or no further executable lines are found
442 * return NULL.
443 *
444 * Set eflag to 0 if line is not to be echoed else 1
445 */
446 LPTSTR ReadBatchLine ()
447 {
448 TRACE ("ReadBatchLine ()\n");
449
450 /* User halt */
451 if (CheckCtrlBreak (BREAK_BATCHFILE))
452 {
453 while (bc)
454 ExitBatch();
455 return NULL;
456 }
457
458 if (!BatchGetString (textline, sizeof (textline) / sizeof (textline[0]) - 1))
459 {
460 TRACE ("ReadBatchLine(): Reached EOF!\n");
461 /* End of file.... */
462 ExitBatch();
463 return NULL;
464 }
465
466 TRACE ("ReadBatchLine(): textline: \'%s\'\n", debugstr_aw(textline));
467
468 if (textline[_tcslen(textline) - 1] != _T('\n'))
469 _tcscat(textline, _T("\n"));
470
471 return textline;
472 }
473
474 /* EOF */