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 WARN("Cannot allocate memory for dp!\n"); 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 * free the allocated memory of a batch file 161 */ 162 VOID ClearBatch(VOID) 163 { 164 TRACE ("ClearBatch mem = %08x free = %d\n", bc->mem, bc->memfree); 165 166 if (bc->mem && bc->memfree) 167 cmd_free(bc->mem); 168 169 if (bc->raw_params) 170 cmd_free(bc->raw_params); 171 172 if (bc->params) 173 cmd_free(bc->params); 174 } 175 176 /* 177 * If a batch file is current, exits it, freeing the context block and 178 * chaining back to the previous one. 179 * 180 * If no new batch context is found, sets ECHO back ON. 181 * 182 * If the parameter is non-null or not empty, it is printed as an exit 183 * message 184 */ 185 186 VOID ExitBatch(VOID) 187 { 188 ClearBatch(); 189 190 TRACE ("ExitBatch\n"); 191 192 UndoRedirection(bc->RedirList, NULL); 193 FreeRedirection(bc->RedirList); 194 195 /* Preserve echo state across batch calls */ 196 bEcho = bc->bEcho; 197 198 while (bc->setlocal) 199 cmd_endlocal(_T("")); 200 201 bc = bc->prev; 202 } 203 204 /* 205 * Load batch file into memory 206 * 207 */ 208 void BatchFile2Mem(HANDLE hBatchFile) 209 { 210 TRACE ("BatchFile2Mem ()\n"); 211 212 bc->memsize = GetFileSize(hBatchFile, NULL); 213 bc->mem = (char *)cmd_alloc(bc->memsize+1); /* 1 extra for '\0' */ 214 215 /* if memory is available, read it in and close the file */ 216 if (bc->mem != NULL) 217 { 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 */ 223 } 224 else 225 { 226 bc->memsize=0; /* this will prevent mem being accessed */ 227 bc->memfree=FALSE; 228 } 229 bc->mempos = 0; /* set position to the start */ 230 } 231 232 /* 233 * Start batch file execution 234 * 235 * The firstword parameter is the full filename of the batch file. 236 * 237 */ 238 INT Batch(LPTSTR fullname, LPTSTR firstword, LPTSTR param, PARSED_COMMAND *Cmd) 239 { 240 BATCH_CONTEXT new; 241 LPFOR_CONTEXT saved_fc; 242 INT i; 243 INT ret = 0; 244 BOOL same_fn = FALSE; 245 246 HANDLE hFile = 0; 247 SetLastError(0); 248 if (bc && bc->mem) 249 { 250 TCHAR fpname[MAX_PATH]; 251 GetFullPathName(fullname, sizeof(fpname) / sizeof(TCHAR), fpname, NULL); 252 if (_tcsicmp(bc->BatchFilePath,fpname)==0) 253 same_fn=TRUE; 254 } 255 TRACE ("Batch: (\'%s\', \'%s\', \'%s\') same_fn = %d\n", 256 debugstr_aw(fullname), debugstr_aw(firstword), debugstr_aw(param), same_fn); 257 258 if (!same_fn) 259 { 260 hFile = CreateFile(fullname, GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, 261 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | 262 FILE_FLAG_SEQUENTIAL_SCAN, NULL); 263 264 if (hFile == INVALID_HANDLE_VALUE) 265 { 266 ConErrResPuts(STRING_BATCH_ERROR); 267 return 1; 268 } 269 } 270 271 if (bc != NULL && Cmd == bc->current) 272 { 273 /* Then we are transferring to another batch */ 274 ClearBatch(); 275 AddBatchRedirection(&Cmd->Redirections); 276 } 277 else 278 { 279 struct _SETLOCAL *setlocal = NULL; 280 281 if (Cmd == NULL) 282 { 283 /* This is a CALL. CALL will set errorlevel to our return value, so 284 * in order to keep the value of errorlevel unchanged in the case 285 * of calling an empty batch file, we must return that same value. */ 286 ret = nErrorLevel; 287 } 288 else if (bc) 289 { 290 /* If a batch file runs another batch file as part of a compound command 291 * (e.g. "x.bat & somethingelse") then the first file gets terminated. */ 292 293 /* Get its SETLOCAL stack so it can be migrated to the new context */ 294 setlocal = bc->setlocal; 295 bc->setlocal = NULL; 296 ExitBatch(); 297 } 298 299 /* Create a new context. This function will not 300 * return until this context has been exited */ 301 new.prev = bc; 302 /* copy some fields in the new structure if it is the same file */ 303 if (same_fn) 304 { 305 new.mem = bc->mem; 306 new.memsize = bc->memsize; 307 new.mempos = 0; 308 new.memfree = FALSE; /* don't free this, being used before this */ 309 } 310 bc = &new; 311 bc->RedirList = NULL; 312 bc->setlocal = setlocal; 313 } 314 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 */ 317 if (!same_fn) 318 { 319 BatchFile2Mem(hFile); 320 CloseHandle(hFile); 321 } 322 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; 327 328 bc->params = BatchParams (firstword, param); 329 // 330 // Allocate enough memory to hold the params and copy them over without modifications 331 // 332 bc->raw_params = cmd_dup(param); 333 if (bc->raw_params == NULL) 334 { 335 error_out_of_memory(); 336 return 1; 337 } 338 339 /* Check if this is a "CALL :label" */ 340 if (*firstword == _T(':')) 341 cmd_goto(firstword); 342 343 /* If we are calling from inside a FOR, hide the FOR variables */ 344 saved_fc = fc; 345 fc = NULL; 346 347 /* If we have created a new context, don't return 348 * until this batch file has completed. */ 349 while (bc == &new && !bExit) 350 { 351 Cmd = ParseCommand(NULL); 352 if (!Cmd) 353 continue; 354 355 /* JPP 19980807 */ 356 /* Echo batch file line */ 357 if (bEcho && !bDisableBatchEcho && Cmd->Type != C_QUIET) 358 { 359 if (!bIgnoreEcho) 360 ConOutChar(_T('\n')); 361 PrintPrompt(); 362 EchoCommand(Cmd); 363 ConOutChar(_T('\n')); 364 } 365 366 bc->current = Cmd; 367 ret = ExecuteCommand(Cmd); 368 FreeCommand(Cmd); 369 } 370 371 /* Always return the current errorlevel */ 372 ret = nErrorLevel; 373 374 TRACE ("Batch: returns TRUE\n"); 375 376 fc = saved_fc; 377 return ret; 378 } 379 380 VOID AddBatchRedirection(REDIRECTION **RedirList) 381 { 382 REDIRECTION **ListEnd; 383 384 /* Prepend the list to the batch context's list */ 385 ListEnd = RedirList; 386 while (*ListEnd) 387 ListEnd = &(*ListEnd)->Next; 388 *ListEnd = bc->RedirList; 389 bc->RedirList = *RedirList; 390 391 /* Null out the pointer so that the list will not be cleared prematurely. 392 * These redirections should persist until the batch file exits. */ 393 *RedirList = NULL; 394 } 395 396 /* 397 * Read a single line from the batch file from the current batch/memory position. 398 * Almost a copy of FileGetString with same UNICODE handling 399 */ 400 BOOL BatchGetString(LPTSTR lpBuffer, INT nBufferLength) 401 { 402 LPSTR lpString; 403 INT len = 0; 404 #ifdef _UNICODE 405 lpString = cmd_alloc(nBufferLength); 406 if (!lpString) 407 { 408 WARN("Cannot allocate memory for lpString\n"); 409 error_out_of_memory(); 410 return FALSE; 411 } 412 #else 413 lpString = lpBuffer; 414 #endif 415 /* read all chars from memory until a '\n' is encountered */ 416 if (bc->mem) 417 { 418 for (; (bc->mempos < bc->memsize && len < (nBufferLength-1)); len++) 419 { 420 lpString[len] = bc->mem[bc->mempos++]; 421 if (lpString[len] == '\n' ) 422 { 423 len++; 424 break; 425 } 426 } 427 } 428 429 if (!len) 430 { 431 #ifdef _UNICODE 432 cmd_free(lpString); 433 #endif 434 return FALSE; 435 } 436 437 lpString[len++] = '\0'; 438 #ifdef _UNICODE 439 MultiByteToWideChar(OutputCodePage, 0, lpString, -1, lpBuffer, len); 440 cmd_free(lpString); 441 #endif 442 return TRUE; 443 } 444 445 /* 446 * Read and return the next executable line form the current batch file 447 * 448 * If no batch file is current or no further executable lines are found 449 * return NULL. 450 * 451 * Set eflag to 0 if line is not to be echoed else 1 452 */ 453 LPTSTR ReadBatchLine(VOID) 454 { 455 TRACE ("ReadBatchLine ()\n"); 456 457 /* User halt */ 458 if (CheckCtrlBreak (BREAK_BATCHFILE)) 459 { 460 while (bc) 461 ExitBatch(); 462 return NULL; 463 } 464 465 if (!BatchGetString (textline, sizeof (textline) / sizeof (textline[0]) - 1)) 466 { 467 TRACE ("ReadBatchLine(): Reached EOF!\n"); 468 /* End of file.... */ 469 ExitBatch(); 470 return NULL; 471 } 472 473 TRACE ("ReadBatchLine(): textline: \'%s\'\n", debugstr_aw(textline)); 474 475 if (textline[_tcslen(textline) - 1] != _T('\n')) 476 _tcscat(textline, _T("\n")); 477 478 return textline; 479 } 480 481 /* EOF */ 482