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 PBATCH_CONTEXT bc = NULL; 67 68 BOOL bEcho = TRUE; /* The echo flag */ 69 70 /* Buffer for reading Batch file lines */ 71 TCHAR textline[BATCH_BUFFSIZE]; 72 73 74 /* 75 * Returns a pointer to the n'th parameter of the current batch file. 76 * If no such parameter exists returns pointer to empty string. 77 * If no batch file is current, returns NULL 78 * 79 */ 80 LPTSTR FindArg(TCHAR Char, BOOL *IsParam0) 81 { 82 LPTSTR pp; 83 INT n = Char - _T('0'); 84 85 TRACE ("FindArg: (%d)\n", n); 86 87 if (n < 0 || n > 9) 88 return NULL; 89 90 n = bc->shiftlevel[n]; 91 *IsParam0 = (n == 0); 92 pp = bc->params; 93 94 /* Step up the strings till we reach the end */ 95 /* or the one we want */ 96 while (*pp && n--) 97 pp += _tcslen (pp) + 1; 98 99 return pp; 100 } 101 102 103 /* 104 * Batch_params builds a parameter list in newly allocated memory. 105 * The parameters consist of null terminated strings with a final 106 * NULL character signalling the end of the parameters. 107 * 108 */ 109 static LPTSTR BatchParams(LPTSTR s1, LPTSTR s2) 110 { 111 LPTSTR dp = (LPTSTR)cmd_alloc((_tcslen(s1) + _tcslen(s2) + 3) * sizeof (TCHAR)); 112 113 /* JPP 20-Jul-1998 added error checking */ 114 if (dp == NULL) 115 { 116 WARN("Cannot allocate memory for dp!\n"); 117 error_out_of_memory(); 118 return NULL; 119 } 120 121 if (s1 && *s1) 122 { 123 s1 = _stpcpy (dp, s1); 124 *s1++ = _T('\0'); 125 } 126 else 127 s1 = dp; 128 129 while (*s2) 130 { 131 BOOL inquotes = FALSE; 132 133 /* Find next parameter */ 134 while (_istspace(*s2) || (*s2 && _tcschr(STANDARD_SEPS, *s2))) 135 s2++; 136 if (!*s2) 137 break; 138 139 /* Copy it */ 140 do 141 { 142 if (!inquotes && (_istspace(*s2) || _tcschr(STANDARD_SEPS, *s2))) 143 break; 144 inquotes ^= (*s2 == _T('"')); 145 *s1++ = *s2++; 146 } while (*s2); 147 *s1++ = _T('\0'); 148 } 149 150 *s1 = _T('\0'); 151 152 return dp; 153 } 154 155 /* 156 * Free the allocated memory of a batch file. 157 */ 158 static VOID ClearBatch(VOID) 159 { 160 TRACE("ClearBatch mem = %08x ; free = %d\n", bc->mem, bc->memfree); 161 162 if (bc->mem && bc->memfree) 163 cmd_free(bc->mem); 164 165 if (bc->raw_params) 166 cmd_free(bc->raw_params); 167 168 if (bc->params) 169 cmd_free(bc->params); 170 } 171 172 /* 173 * If a batch file is current, exits it, freeing the context block and 174 * chaining back to the previous one. 175 * 176 * If no new batch context is found, sets ECHO back ON. 177 * 178 * If the parameter is non-null or not empty, it is printed as an exit 179 * message 180 */ 181 182 VOID ExitBatch(VOID) 183 { 184 ClearBatch(); 185 186 TRACE("ExitBatch\n"); 187 188 UndoRedirection(bc->RedirList, NULL); 189 FreeRedirection(bc->RedirList); 190 191 /* Preserve echo state across batch calls */ 192 bEcho = bc->bEcho; 193 194 while (bc->setlocal) 195 cmd_endlocal(_T("")); 196 197 bc = bc->prev; 198 199 #if 0 200 /* Do not process any more parts of a compound command */ 201 bc->current = NULL; 202 #endif 203 204 /* If there is no more batch contexts, notify the signal handler */ 205 if (!bc) 206 CheckCtrlBreak(BREAK_OUTOFBATCH); 207 } 208 209 /* 210 * Exit all the nested batch calls. 211 */ 212 VOID ExitAllBatches(VOID) 213 { 214 while (bc) 215 ExitBatch(); 216 } 217 218 /* 219 * Load batch file into memory. 220 */ 221 static void BatchFile2Mem(HANDLE hBatchFile) 222 { 223 TRACE("BatchFile2Mem()\n"); 224 225 bc->memsize = GetFileSize(hBatchFile, NULL); 226 bc->mem = (char *)cmd_alloc(bc->memsize+1); /* 1 extra for '\0' */ 227 228 /* if memory is available, read it in and close the file */ 229 if (bc->mem != NULL) 230 { 231 TRACE ("BatchFile2Mem memory %08x - %08x\n",bc->mem,bc->memsize); 232 SetFilePointer (hBatchFile, 0, NULL, FILE_BEGIN); 233 ReadFile(hBatchFile, (LPVOID)bc->mem, bc->memsize, &bc->memsize, NULL); 234 bc->mem[bc->memsize]='\0'; /* end this, so you can dump it as a string */ 235 bc->memfree=TRUE; /* this one needs to be freed */ 236 } 237 else 238 { 239 bc->memsize=0; /* this will prevent mem being accessed */ 240 bc->memfree=FALSE; 241 } 242 bc->mempos = 0; /* set position to the start */ 243 } 244 245 /* 246 * Start batch file execution. 247 * 248 * The firstword parameter is the full filename of the batch file. 249 */ 250 INT Batch(LPTSTR fullname, LPTSTR firstword, LPTSTR param, PARSED_COMMAND *Cmd) 251 { 252 INT ret = 0; 253 INT i; 254 HANDLE hFile = NULL; 255 BOOL bSameFn = FALSE; 256 BATCH_CONTEXT new; 257 PFOR_CONTEXT saved_fc; 258 259 SetLastError(0); 260 if (bc && bc->mem) 261 { 262 TCHAR fpname[MAX_PATH]; 263 GetFullPathName(fullname, ARRAYSIZE(fpname), fpname, NULL); 264 if (_tcsicmp(bc->BatchFilePath, fpname) == 0) 265 bSameFn = TRUE; 266 } 267 TRACE("Batch(\'%s\', \'%s\', \'%s\') bSameFn = %d\n", 268 debugstr_aw(fullname), debugstr_aw(firstword), debugstr_aw(param), bSameFn); 269 270 if (!bSameFn) 271 { 272 hFile = CreateFile(fullname, GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, 273 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | 274 FILE_FLAG_SEQUENTIAL_SCAN, NULL); 275 276 if (hFile == INVALID_HANDLE_VALUE) 277 { 278 ConErrResPuts(STRING_BATCH_ERROR); 279 return 1; 280 } 281 } 282 283 if (bc != NULL && Cmd == bc->current) 284 { 285 /* Then we are transferring to another batch */ 286 ClearBatch(); 287 AddBatchRedirection(&Cmd->Redirections); 288 } 289 else 290 { 291 struct _SETLOCAL *setlocal = NULL; 292 293 if (Cmd == NULL) 294 { 295 /* This is a CALL. CALL will set errorlevel to our return value, so 296 * in order to keep the value of errorlevel unchanged in the case 297 * of calling an empty batch file, we must return that same value. */ 298 ret = nErrorLevel; 299 } 300 else if (bc) 301 { 302 /* If a batch file runs another batch file as part of a compound command 303 * (e.g. "x.bat & somethingelse") then the first file gets terminated. */ 304 305 /* Get its SETLOCAL stack so it can be migrated to the new context */ 306 setlocal = bc->setlocal; 307 bc->setlocal = NULL; 308 ExitBatch(); 309 } 310 311 /* Create a new context. This function will not 312 * return until this context has been exited */ 313 new.prev = bc; 314 /* copy some fields in the new structure if it is the same file */ 315 if (bSameFn) 316 { 317 new.mem = bc->mem; 318 new.memsize = bc->memsize; 319 new.mempos = 0; 320 new.memfree = FALSE; /* don't free this, being used before this */ 321 } 322 bc = &new; 323 bc->RedirList = NULL; 324 bc->setlocal = setlocal; 325 } 326 327 GetFullPathName(fullname, ARRAYSIZE(bc->BatchFilePath), bc->BatchFilePath, NULL); 328 329 /* If a new batch file, load it into memory and close the file */ 330 if (!bSameFn) 331 { 332 BatchFile2Mem(hFile); 333 CloseHandle(hFile); 334 } 335 336 bc->mempos = 0; /* Go to the beginning of the batch file */ 337 bc->bEcho = bEcho; /* Preserve echo across batch calls */ 338 for (i = 0; i < 10; i++) 339 bc->shiftlevel[i] = i; 340 341 /* Parse the parameters and make a raw copy of them without modifications */ 342 bc->params = BatchParams(firstword, param); 343 bc->raw_params = cmd_dup(param); 344 if (bc->raw_params == NULL) 345 { 346 error_out_of_memory(); 347 return 1; 348 } 349 350 /* Check if this is a "CALL :label" */ 351 if (*firstword == _T(':')) 352 ret = cmd_goto(firstword); 353 354 /* If we are calling from inside a FOR, hide the FOR variables */ 355 saved_fc = fc; 356 fc = NULL; 357 358 /* If we have created a new context, don't return 359 * until this batch file has completed. */ 360 while (bc == &new && !bExit) 361 { 362 Cmd = ParseCommand(NULL); 363 if (!Cmd) 364 { 365 if (!bParseError) 366 continue; 367 368 /* Echo the pre-parsed batch file line on error */ 369 if (bEcho && !bDisableBatchEcho) 370 { 371 if (!bIgnoreEcho) 372 ConOutChar(_T('\n')); 373 PrintPrompt(); 374 ConOutPuts(ParseLine); 375 ConOutChar(_T('\n')); 376 } 377 /* Stop all execution */ 378 ExitAllBatches(); 379 ret = 1; 380 break; 381 } 382 383 /* JPP 19980807 */ 384 /* Echo the command and execute it */ 385 bc->current = Cmd; 386 ret = ExecuteCommandWithEcho(Cmd); 387 FreeCommand(Cmd); 388 } 389 390 /* Restore the FOR variables */ 391 fc = saved_fc; 392 393 /* Always return the last command's return code */ 394 TRACE("Batch: returns %d\n", ret); 395 return ret; 396 } 397 398 VOID AddBatchRedirection(REDIRECTION **RedirList) 399 { 400 REDIRECTION **ListEnd; 401 402 /* Prepend the list to the batch context's list */ 403 ListEnd = RedirList; 404 while (*ListEnd) 405 ListEnd = &(*ListEnd)->Next; 406 *ListEnd = bc->RedirList; 407 bc->RedirList = *RedirList; 408 409 /* Null out the pointer so that the list will not be cleared prematurely. 410 * These redirections should persist until the batch file exits. */ 411 *RedirList = NULL; 412 } 413 414 /* 415 * Read a single line from the batch file from the current batch/memory position. 416 * Almost a copy of FileGetString with same UNICODE handling 417 */ 418 BOOL BatchGetString(LPTSTR lpBuffer, INT nBufferLength) 419 { 420 LPSTR lpString; 421 INT len = 0; 422 #ifdef _UNICODE 423 lpString = cmd_alloc(nBufferLength); 424 if (!lpString) 425 { 426 WARN("Cannot allocate memory for lpString\n"); 427 error_out_of_memory(); 428 return FALSE; 429 } 430 #else 431 lpString = lpBuffer; 432 #endif 433 /* read all chars from memory until a '\n' is encountered */ 434 if (bc->mem) 435 { 436 for (; (bc->mempos < bc->memsize && len < (nBufferLength-1)); len++) 437 { 438 lpString[len] = bc->mem[bc->mempos++]; 439 if (lpString[len] == '\n' ) 440 { 441 len++; 442 break; 443 } 444 } 445 } 446 447 if (!len) 448 { 449 #ifdef _UNICODE 450 cmd_free(lpString); 451 #endif 452 return FALSE; 453 } 454 455 lpString[len++] = '\0'; 456 #ifdef _UNICODE 457 MultiByteToWideChar(OutputCodePage, 0, lpString, -1, lpBuffer, len); 458 cmd_free(lpString); 459 #endif 460 return TRUE; 461 } 462 463 /* 464 * Read and return the next executable line form the current batch file 465 * 466 * If no batch file is current or no further executable lines are found 467 * return NULL. 468 * 469 * Set eflag to 0 if line is not to be echoed else 1 470 */ 471 LPTSTR ReadBatchLine(VOID) 472 { 473 TRACE("ReadBatchLine()\n"); 474 475 /* User halt */ 476 if (CheckCtrlBreak(BREAK_BATCHFILE)) 477 { 478 ExitAllBatches(); 479 return NULL; 480 } 481 482 if (!BatchGetString(textline, ARRAYSIZE(textline) - 1)) 483 { 484 TRACE("ReadBatchLine(): Reached EOF!\n"); 485 /* End of file */ 486 ExitBatch(); 487 return NULL; 488 } 489 490 TRACE("ReadBatchLine(): textline: \'%s\'\n", debugstr_aw(textline)); 491 492 if (textline[_tcslen(textline) - 1] != _T('\n')) 493 _tcscat(textline, _T("\n")); 494 495 return textline; 496 } 497 498 /* EOF */ 499