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 * Seperated 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 hardcode string to En.rc
64 /* The stack of current batch contexts.
65 * NULL when no batch is active
67 LPBATCH_CONTEXT bc
= NULL
;
69 BOOL bEcho
= TRUE
; /* The echo flag */
73 /* Buffer for reading Batch file lines */
74 TCHAR textline
[BATCH_BUFFSIZE
];
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
84 LPTSTR
FindArg(TCHAR Char
, BOOL
*IsParam0
)
87 INT n
= Char
- _T('0');
89 TRACE ("FindArg: (%d)\n", n
);
94 n
= bc
->shiftlevel
[n
];
98 /* Step up the strings till we reach the end */
99 /* or the one we want */
101 pp
+= _tcslen (pp
) + 1;
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.
114 LPTSTR
BatchParams (LPTSTR s1
, LPTSTR s2
)
116 LPTSTR dp
= (LPTSTR
)cmd_alloc ((_tcslen(s1
) + _tcslen(s2
) + 3) * sizeof (TCHAR
));
118 /* JPP 20-Jul-1998 added error checking */
121 error_out_of_memory();
127 s1
= _stpcpy (dp
, s1
);
135 BOOL inquotes
= FALSE
;
137 /* Find next parameter */
138 while (_istspace(*s2
) || (*s2
&& _tcschr(_T(",;="), *s2
)))
146 if (!inquotes
&& (_istspace(*s2
) || _tcschr(_T(",;="), *s2
)))
148 inquotes
^= (*s2
== _T('"'));
160 * free the allocated memory of a batch file
164 TRACE ("ClearBatch mem = %08x free = %d\n", bc
->mem
, bc
->memfree
);
166 if (bc
->mem
&& bc
->memfree
)
170 cmd_free(bc
->raw_params
);
173 cmd_free(bc
->params
);
177 * If a batch file is current, exits it, freeing the context block and
178 * chaining back to the previous one.
180 * If no new batch context is found, sets ECHO back ON.
182 * If the parameter is non-null or not empty, it is printed as an exit
190 TRACE ("ExitBatch\n");
192 UndoRedirection(bc
->RedirList
, NULL
);
193 FreeRedirection(bc
->RedirList
);
195 /* Preserve echo state across batch calls */
199 cmd_endlocal(_T(""));
205 * Load batch file into memory
208 void BatchFile2Mem(HANDLE hBatchFile
)
210 TRACE ("BatchFile2Mem ()\n");
212 bc
->memsize
= GetFileSize(hBatchFile
, NULL
);
213 bc
->mem
= (char *)cmd_alloc(bc
->memsize
+1); /* 1 extra for '\0' */
215 /* if memory is available, read it in and close the file */
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 */
226 bc
->memsize
=0; /* this will prevent mem being accessed */
229 bc
->mempos
= 0; /* set position to the start */
233 * Start batch file execution
235 * The firstword parameter is the full filename of the batch file.
239 INT
Batch (LPTSTR fullname
, LPTSTR firstword
, LPTSTR param
, PARSED_COMMAND
*Cmd
)
242 LPFOR_CONTEXT saved_fc
;
245 BOOL same_fn
= FALSE
;
251 TCHAR fpname
[MAX_PATH
];
252 GetFullPathName(fullname
, sizeof(fpname
) / sizeof(TCHAR
), fpname
, NULL
);
253 if (_tcsicmp(bc
->BatchFilePath
,fpname
)==0)
256 TRACE ("Batch: (\'%s\', \'%s\', \'%s\') same_fn = %d\n",
257 debugstr_aw(fullname
), debugstr_aw(firstword
), debugstr_aw(param
), same_fn
);
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
);
265 if (hFile
== INVALID_HANDLE_VALUE
)
267 ConErrResPuts(STRING_BATCH_ERROR
);
272 if (bc
!= NULL
&& Cmd
== bc
->current
)
274 /* Then we are transferring to another batch */
276 AddBatchRedirection(&Cmd
->Redirections
);
280 struct _SETLOCAL
*setlocal
= NULL
;
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. */
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. */
294 /* Get its SETLOCAL stack so it can be migrated to the new context */
295 setlocal
= bc
->setlocal
;
300 /* Create a new context. This function will not
301 * return until this context has been exited */
303 /* copy some fields in the new structure if it is the same file */
306 new.memsize
= bc
->memsize
;
308 new.memfree
= FALSE
; /* don't free this, being used before this */
311 bc
->RedirList
= NULL
;
312 bc
->setlocal
= setlocal
;
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 */
319 BatchFile2Mem(hFile
);
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
;
328 bc
->params
= BatchParams (firstword
, param
);
330 // Allocate enough memory to hold the params and copy them over without modifications
332 bc
->raw_params
= cmd_dup(param
);
333 if (bc
->raw_params
== NULL
)
335 error_out_of_memory();
339 /* Check if this is a "CALL :label" */
340 if (*firstword
== _T(':'))
343 /* If we are calling from inside a FOR, hide the FOR variables */
347 /* If we have created a new context, don't return
348 * until this batch file has completed. */
349 while (bc
== &new && !bExit
)
351 Cmd
= ParseCommand(NULL
);
356 /* Echo batch file line */
357 if (bEcho
&& !bDisableBatchEcho
&& Cmd
->Type
!= C_QUIET
)
360 ConOutChar(_T('\n'));
363 ConOutChar(_T('\n'));
367 ret
= ExecuteCommand(Cmd
);
371 TRACE ("Batch: returns TRUE\n");
377 VOID
AddBatchRedirection(REDIRECTION
**RedirList
)
379 REDIRECTION
**ListEnd
;
381 /* Prepend the list to the batch context's list */
384 ListEnd
= &(*ListEnd
)->Next
;
385 *ListEnd
= bc
->RedirList
;
386 bc
->RedirList
= *RedirList
;
388 /* Null out the pointer so that the list will not be cleared prematurely.
389 * These redirections should persist until the batch file exits. */
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
397 BOOL
BatchGetString (LPTSTR lpBuffer
, INT nBufferLength
)
402 lpString
= cmd_alloc(nBufferLength
);
406 /* read all chars from memory until a '\n' is encountered */
409 for (; (bc
->mempos
< bc
->memsize
&& len
< (nBufferLength
-1)); len
++)
411 lpString
[len
] = bc
->mem
[bc
->mempos
++];
412 if (lpString
[len
] == '\n' )
428 lpString
[len
++] = '\0';
430 MultiByteToWideChar(OutputCodePage
, 0, lpString
, -1, lpBuffer
, len
);
437 * Read and return the next executable line form the current batch file
439 * If no batch file is current or no further executable lines are found
442 * Set eflag to 0 if line is not to be echoed else 1
444 LPTSTR
ReadBatchLine ()
446 TRACE ("ReadBatchLine ()\n");
449 if (CheckCtrlBreak (BREAK_BATCHFILE
))
456 if (!BatchGetString (textline
, sizeof (textline
) / sizeof (textline
[0]) - 1))
458 TRACE ("ReadBatchLine(): Reached EOF!\n");
459 /* End of file.... */
464 TRACE ("ReadBatchLine(): textline: \'%s\'\n", debugstr_aw(textline
));
466 if (textline
[_tcslen(textline
) - 1] != _T('\n'))
467 _tcscat(textline
, _T("\n"));