- Remove the remaining __USE_W32API, deprecated for ages.
[reactos.git] / reactos / 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 /*
161 * If a batch file is current, exits it, freeing the context block and
162 * chaining back to the previous one.
163 *
164 * If no new batch context is found, sets ECHO back ON.
165 *
166 * If the parameter is non-null or not empty, it is printed as an exit
167 * message
168 */
169
170 VOID ExitBatch()
171 {
172 TRACE ("ExitBatch\n");
173
174 if (bc->hBatchFile)
175 {
176 CloseHandle (bc->hBatchFile);
177 bc->hBatchFile = INVALID_HANDLE_VALUE;
178 }
179
180 if (bc->raw_params)
181 cmd_free(bc->raw_params);
182
183 if (bc->params)
184 cmd_free(bc->params);
185
186 UndoRedirection(bc->RedirList, NULL);
187 FreeRedirection(bc->RedirList);
188
189 /* Preserve echo state across batch calls */
190 bEcho = bc->bEcho;
191
192 while (bc->setlocal)
193 cmd_endlocal(_T(""));
194
195 bc = bc->prev;
196 }
197
198
199 /*
200 * Start batch file execution
201 *
202 * The firstword parameter is the full filename of the batch file.
203 *
204 */
205
206 INT Batch (LPTSTR fullname, LPTSTR firstword, LPTSTR param, PARSED_COMMAND *Cmd)
207 {
208 BATCH_CONTEXT new;
209 LPFOR_CONTEXT saved_fc;
210 INT i;
211 INT ret = 0;
212
213 HANDLE hFile;
214 SetLastError(0);
215 hFile = CreateFile (fullname, GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, NULL,
216 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL |
217 FILE_FLAG_SEQUENTIAL_SCAN, NULL);
218
219 TRACE ("Batch: (\'%s\', \'%s\', \'%s\') hFile = %x\n",
220 debugstr_aw(fullname), debugstr_aw(firstword), debugstr_aw(param), hFile);
221
222 if (hFile == INVALID_HANDLE_VALUE)
223 {
224 ConErrResPuts(STRING_BATCH_ERROR);
225 return 1;
226 }
227
228 if (bc != NULL && Cmd == bc->current)
229 {
230 /* Then we are transferring to another batch */
231 CloseHandle (bc->hBatchFile);
232 bc->hBatchFile = INVALID_HANDLE_VALUE;
233 if (bc->params)
234 cmd_free (bc->params);
235 if (bc->raw_params)
236 cmd_free (bc->raw_params);
237 AddBatchRedirection(&Cmd->Redirections);
238 }
239 else
240 {
241 struct _SETLOCAL *setlocal = NULL;
242
243 if (Cmd == NULL)
244 {
245 /* This is a CALL. CALL will set errorlevel to our return value, so
246 * in order to keep the value of errorlevel unchanged in the case
247 * of calling an empty batch file, we must return that same value. */
248 ret = nErrorLevel;
249 }
250 else if (bc)
251 {
252 /* If a batch file runs another batch file as part of a compound command
253 * (e.g. "x.bat & somethingelse") then the first file gets terminated. */
254
255 /* Get its SETLOCAL stack so it can be migrated to the new context */
256 setlocal = bc->setlocal;
257 bc->setlocal = NULL;
258 ExitBatch();
259 }
260
261 /* Create a new context. This function will not
262 * return until this context has been exited */
263 new.prev = bc;
264 bc = &new;
265 bc->RedirList = NULL;
266 bc->setlocal = setlocal;
267 }
268
269 GetFullPathName(fullname, sizeof(bc->BatchFilePath) / sizeof(TCHAR), bc->BatchFilePath, NULL);
270
271 bc->hBatchFile = hFile;
272 SetFilePointer (bc->hBatchFile, 0, NULL, FILE_BEGIN);
273 bc->bEcho = bEcho; /* Preserve echo across batch calls */
274 for (i = 0; i < 10; i++)
275 bc->shiftlevel[i] = i;
276
277 bc->params = BatchParams (firstword, param);
278 //
279 // Allocate enough memory to hold the params and copy them over without modifications
280 //
281 bc->raw_params = cmd_dup(param);
282 if (bc->raw_params == NULL)
283 {
284 error_out_of_memory();
285 return 1;
286 }
287
288 /* Check if this is a "CALL :label" */
289 if (*firstword == _T(':'))
290 cmd_goto(firstword);
291
292 /* If we are calling from inside a FOR, hide the FOR variables */
293 saved_fc = fc;
294 fc = NULL;
295
296 /* If we have created a new context, don't return
297 * until this batch file has completed. */
298 while (bc == &new && !bExit)
299 {
300 Cmd = ParseCommand(NULL);
301 if (!Cmd)
302 continue;
303
304 /* JPP 19980807 */
305 /* Echo batch file line */
306 if (bEcho && !bDisableBatchEcho && Cmd->Type != C_QUIET)
307 {
308 if (!bIgnoreEcho)
309 ConOutChar(_T('\n'));
310 PrintPrompt();
311 EchoCommand(Cmd);
312 ConOutChar(_T('\n'));
313 }
314
315 bc->current = Cmd;
316 ret = ExecuteCommand(Cmd);
317 FreeCommand(Cmd);
318 }
319
320 TRACE ("Batch: returns TRUE\n");
321
322 fc = saved_fc;
323 return ret;
324 }
325
326 VOID AddBatchRedirection(REDIRECTION **RedirList)
327 {
328 REDIRECTION **ListEnd;
329
330 /* Prepend the list to the batch context's list */
331 ListEnd = RedirList;
332 while (*ListEnd)
333 ListEnd = &(*ListEnd)->Next;
334 *ListEnd = bc->RedirList;
335 bc->RedirList = *RedirList;
336
337 /* Null out the pointer so that the list will not be cleared prematurely.
338 * These redirections should persist until the batch file exits. */
339 *RedirList = NULL;
340 }
341
342 /*
343 * Read and return the next executable line form the current batch file
344 *
345 * If no batch file is current or no further executable lines are found
346 * return NULL.
347 *
348 * Set eflag to 0 if line is not to be echoed else 1
349 */
350
351 LPTSTR ReadBatchLine ()
352 {
353 TRACE ("ReadBatchLine ()\n");
354
355 /* User halt */
356 if (CheckCtrlBreak (BREAK_BATCHFILE))
357 {
358 while (bc)
359 ExitBatch();
360 return NULL;
361 }
362
363 if (!FileGetString (bc->hBatchFile, textline, sizeof (textline) / sizeof (textline[0]) - 1))
364 {
365 TRACE ("ReadBatchLine(): Reached EOF!\n");
366 /* End of file.... */
367 ExitBatch();
368 return NULL;
369 }
370
371 TRACE ("ReadBatchLine(): textline: \'%s\'\n", debugstr_aw(textline));
372
373 if (textline[_tcslen(textline) - 1] != _T('\n'))
374 _tcscat(textline, _T("\n"));
375
376 return textline;
377 }
378
379 /* EOF */