2 * BATCH.C - batch file processor for CMD.EXE.
7 * ??/??/?? (Evan Jeffrey)
10 * 15 Jul 1995 (Tim Norman)
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.
17 * i have added some constants to help making changes easier.
19 * 29 Jan 1996 (Steffan Kaiser)
20 * made a few cosmetic changes
22 * 05 Feb 1996 (Tim Norman)
23 * changed to comply with new first/rest calling scheme
25 * 14 Jun 1997 (Steffen Kaiser)
26 * bug fixes. added error level expansion %?. ctrl-break handling
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.
32 * 16 Jul 1998 (John P Price <linux-guru@gcfl.net>)
33 * Separated commands into individual files.
35 * 19 Jul 1998 (Hans B Pufal) [HBP_001]
36 * Preserve state of echo flag across batch calls.
38 * 19 Jul 1998 (Hans B Pufal) [HBP_002]
39 * Implementation of FOR command
41 * 20-Jul-1998 (John P Price <linux-guru@gcfl.net>)
42 * added error checking after cmd_alloc calls
44 * 27-Jul-1998 (John P Price <linux-guru@gcfl.net>)
45 * added config.h include
47 * 02-Aug-1998 (Hans B Pufal) [HBP_003]
48 * Fixed bug in ECHO flag restoration at exit from batch file
50 * 26-Jan-1999 Eric Kohl
51 * Replaced CRT io functions by Win32 io functions.
54 * 23-Feb-2001 (Carl Nettelblad <cnettel@hem.passagen.es>)
55 * Fixes made to get "for" working.
57 * 02-Apr-2005 (Magnus Olsen <magnus@greatlord.com>)
58 * Remove all hardcoded strings in En.rc
63 /* The stack of current batch contexts.
64 * NULL when no batch is active
66 LPBATCH_CONTEXT bc
= NULL
;
68 BOOL bEcho
= TRUE
; /* The echo flag */
72 /* Buffer for reading Batch file lines */
73 TCHAR textline
[BATCH_BUFFSIZE
];
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
83 LPTSTR
FindArg(TCHAR Char
, BOOL
*IsParam0
)
86 INT n
= Char
- _T('0');
88 TRACE ("FindArg: (%d)\n", n
);
93 n
= bc
->shiftlevel
[n
];
97 /* Step up the strings till we reach the end */
98 /* or the one we want */
100 pp
+= _tcslen (pp
) + 1;
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.
113 LPTSTR
BatchParams (LPTSTR s1
, LPTSTR s2
)
115 LPTSTR dp
= (LPTSTR
)cmd_alloc ((_tcslen(s1
) + _tcslen(s2
) + 3) * sizeof (TCHAR
));
117 /* JPP 20-Jul-1998 added error checking */
120 error_out_of_memory();
126 s1
= _stpcpy (dp
, s1
);
134 BOOL inquotes
= FALSE
;
136 /* Find next parameter */
137 while (_istspace(*s2
) || (*s2
&& _tcschr(_T(",;="), *s2
)))
145 if (!inquotes
&& (_istspace(*s2
) || _tcschr(_T(",;="), *s2
)))
147 inquotes
^= (*s2
== _T('"'));
159 * free the allocated memory of a batch file
163 TRACE ("ClearBatch mem = %08x free = %d\n", bc
->mem
, bc
->memfree
);
165 if (bc
->mem
&& bc
->memfree
)
169 cmd_free(bc
->raw_params
);
172 cmd_free(bc
->params
);
176 * If a batch file is current, exits it, freeing the context block and
177 * chaining back to the previous one.
179 * If no new batch context is found, sets ECHO back ON.
181 * If the parameter is non-null or not empty, it is printed as an exit
189 TRACE ("ExitBatch\n");
191 UndoRedirection(bc
->RedirList
, NULL
);
192 FreeRedirection(bc
->RedirList
);
194 /* Preserve echo state across batch calls */
198 cmd_endlocal(_T(""));
204 * Load batch file into memory
207 void BatchFile2Mem(HANDLE hBatchFile
)
209 TRACE ("BatchFile2Mem ()\n");
211 bc
->memsize
= GetFileSize(hBatchFile
, NULL
);
212 bc
->mem
= (char *)cmd_alloc(bc
->memsize
+1); /* 1 extra for '\0' */
214 /* if memory is available, read it in and close the file */
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 */
225 bc
->memsize
=0; /* this will prevent mem being accessed */
228 bc
->mempos
= 0; /* set position to the start */
232 * Start batch file execution
234 * The firstword parameter is the full filename of the batch file.
237 INT
Batch (LPTSTR fullname
, LPTSTR firstword
, LPTSTR param
, PARSED_COMMAND
*Cmd
)
240 LPFOR_CONTEXT saved_fc
;
243 BOOL same_fn
= FALSE
;
249 TCHAR fpname
[MAX_PATH
];
250 GetFullPathName(fullname
, sizeof(fpname
) / sizeof(TCHAR
), fpname
, NULL
);
251 if (_tcsicmp(bc
->BatchFilePath
,fpname
)==0)
254 TRACE ("Batch: (\'%s\', \'%s\', \'%s\') same_fn = %d\n",
255 debugstr_aw(fullname
), debugstr_aw(firstword
), debugstr_aw(param
), same_fn
);
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
);
263 if (hFile
== INVALID_HANDLE_VALUE
)
265 ConErrResPuts(STRING_BATCH_ERROR
);
270 if (bc
!= NULL
&& Cmd
== bc
->current
)
272 /* Then we are transferring to another batch */
274 AddBatchRedirection(&Cmd
->Redirections
);
278 struct _SETLOCAL
*setlocal
= NULL
;
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. */
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. */
292 /* Get its SETLOCAL stack so it can be migrated to the new context */
293 setlocal
= bc
->setlocal
;
298 /* Create a new context. This function will not
299 * return until this context has been exited */
301 /* copy some fields in the new structure if it is the same file */
304 new.memsize
= bc
->memsize
;
306 new.memfree
= FALSE
; /* don't free this, being used before this */
309 bc
->RedirList
= NULL
;
310 bc
->setlocal
= setlocal
;
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 */
317 BatchFile2Mem(hFile
);
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
;
326 bc
->params
= BatchParams (firstword
, param
);
328 // Allocate enough memory to hold the params and copy them over without modifications
330 bc
->raw_params
= cmd_dup(param
);
331 if (bc
->raw_params
== NULL
)
333 error_out_of_memory();
337 /* Check if this is a "CALL :label" */
338 if (*firstword
== _T(':'))
341 /* If we are calling from inside a FOR, hide the FOR variables */
345 /* If we have created a new context, don't return
346 * until this batch file has completed. */
347 while (bc
== &new && !bExit
)
349 Cmd
= ParseCommand(NULL
);
354 /* Echo batch file line */
355 if (bEcho
&& !bDisableBatchEcho
&& Cmd
->Type
!= C_QUIET
)
358 ConOutChar(_T('\n'));
361 ConOutChar(_T('\n'));
365 ret
= ExecuteCommand(Cmd
);
369 TRACE ("Batch: returns TRUE\n");
375 VOID
AddBatchRedirection(REDIRECTION
**RedirList
)
377 REDIRECTION
**ListEnd
;
379 /* Prepend the list to the batch context's list */
382 ListEnd
= &(*ListEnd
)->Next
;
383 *ListEnd
= bc
->RedirList
;
384 bc
->RedirList
= *RedirList
;
386 /* Null out the pointer so that the list will not be cleared prematurely.
387 * These redirections should persist until the batch file exits. */
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
395 BOOL
BatchGetString (LPTSTR lpBuffer
, INT nBufferLength
)
400 lpString
= cmd_alloc(nBufferLength
);
404 /* read all chars from memory until a '\n' is encountered */
407 for (; (bc
->mempos
< bc
->memsize
&& len
< (nBufferLength
-1)); len
++)
409 lpString
[len
] = bc
->mem
[bc
->mempos
++];
410 if (lpString
[len
] == '\n' )
426 lpString
[len
++] = '\0';
428 MultiByteToWideChar(OutputCodePage
, 0, lpString
, -1, lpBuffer
, len
);
435 * Read and return the next executable line form the current batch file
437 * If no batch file is current or no further executable lines are found
440 * Set eflag to 0 if line is not to be echoed else 1
442 LPTSTR
ReadBatchLine ()
444 TRACE ("ReadBatchLine ()\n");
447 if (CheckCtrlBreak (BREAK_BATCHFILE
))
454 if (!BatchGetString (textline
, sizeof (textline
) / sizeof (textline
[0]) - 1))
456 TRACE ("ReadBatchLine(): Reached EOF!\n");
457 /* End of file.... */
462 TRACE ("ReadBatchLine(): textline: \'%s\'\n", debugstr_aw(textline
));
464 if (textline
[_tcslen(textline
) - 1] != _T('\n'))
465 _tcscat(textline
, _T("\n"));