1 /* 2 * CMD.C - command-line interface. 3 * 4 * 5 * History: 6 * 7 * 17 Jun 1994 (Tim Norman) 8 * started. 9 * 10 * 08 Aug 1995 (Matt Rains) 11 * I have cleaned up the source code. changes now bring this source 12 * into guidelines for recommended programming practice. 13 * 14 * A added the the standard FreeDOS GNU licence test to the 15 * initialize() function. 16 * 17 * Started to replace puts() with printf(). this will help 18 * standardize output. please follow my lead. 19 * 20 * I have added some constants to help making changes easier. 21 * 22 * 15 Dec 1995 (Tim Norman) 23 * major rewrite of the code to make it more efficient and add 24 * redirection support (finally!) 25 * 26 * 06 Jan 1996 (Tim Norman) 27 * finished adding redirection support! Changed to use our own 28 * exec code (MUCH thanks to Svante Frey!!) 29 * 30 * 29 Jan 1996 (Tim Norman) 31 * added support for CHDIR, RMDIR, MKDIR, and ERASE, as per 32 * suggestion of Steffan Kaiser 33 * 34 * changed "file not found" error message to "bad command or 35 * filename" thanks to Dustin Norman for noticing that confusing 36 * message! 37 * 38 * changed the format to call internal commands (again) so that if 39 * they want to split their commands, they can do it themselves 40 * (none of the internal functions so far need that much power, anyway) 41 * 42 * 27 Aug 1996 (Tim Norman) 43 * added in support for Oliver Mueller's ALIAS command 44 * 45 * 14 Jun 1997 (Steffan Kaiser) 46 * added ctrl-break handling and error level 47 * 48 * 16 Jun 1998 (Rob Lake) 49 * Runs command.com if /P is specified in command line. Command.com 50 * also stays permanent. If /C is in the command line, starts the 51 * program next in the line. 52 * 53 * 21 Jun 1998 (Rob Lake) 54 * Fixed up /C so that arguments for the program 55 * 56 * 08-Jul-1998 (John P. Price) 57 * Now sets COMSPEC environment variable 58 * misc clean up and optimization 59 * added date and time commands 60 * changed to using spawnl instead of exec. exec does not copy the 61 * environment to the child process! 62 * 63 * 14 Jul 1998 (Hans B Pufal) 64 * Reorganised source to be more efficient and to more closely 65 * follow MS-DOS conventions. (eg %..% environment variable 66 * replacement works form command line as well as batch file. 67 * 68 * New organisation also properly support nested batch files. 69 * 70 * New command table structure is half way towards providing a 71 * system in which COMMAND will find out what internal commands 72 * are loaded 73 * 74 * 24 Jul 1998 (Hans B Pufal) [HBP_003] 75 * Fixed return value when called with /C option 76 * 77 * 27 Jul 1998 John P. Price 78 * added config.h include 79 * 80 * 28 Jul 1998 John P. Price 81 * added showcmds function to show commands and options available 82 * 83 * 07-Aug-1998 (John P Price <linux-guru@gcfl.net>) 84 * Fixed carriage return output to better match MSDOS with echo 85 * on or off. (marked with "JPP 19980708") 86 * 87 * 07-Dec-1998 (Eric Kohl) 88 * First ReactOS release. 89 * Extended length of commandline buffers to 512. 90 * 91 * 13-Dec-1998 (Eric Kohl) 92 * Added COMSPEC environment variable. 93 * Added "/t" support (color) on cmd command line. 94 * 95 * 07-Jan-1999 (Eric Kohl) 96 * Added help text ("cmd /?"). 97 * 98 * 25-Jan-1999 (Eric Kohl) 99 * Unicode and redirection safe! 100 * Fixed redirections and piping. 101 * Piping is based on temporary files, but basic support 102 * for anonymous pipes already exists. 103 * 104 * 27-Jan-1999 (Eric Kohl) 105 * Replaced spawnl() by CreateProcess(). 106 * 107 * 22-Oct-1999 (Eric Kohl) 108 * Added break handler. 109 * 110 * 15-Dec-1999 (Eric Kohl) 111 * Fixed current directory 112 * 113 * 28-Dec-1999 (Eric Kohl) 114 * Restore window title after program/batch execution 115 * 116 * 03-Feb-2001 (Eric Kohl) 117 * Workaround because argc[0] is NULL under ReactOS 118 * 119 * 23-Feb-2001 (Carl Nettelblad <cnettel@hem.passagen.se>) 120 * %envvar% replacement conflicted with for. 121 * 122 * 30-Apr-2004 (Filip Navara <xnavara@volny.cz>) 123 * Make MakeSureDirectoryPathExistsEx unicode safe. 124 * 125 * 28-Mai-2004 126 * Removed MakeSureDirectoryPathExistsEx. 127 * Use the current directory if GetTempPath fails. 128 * 129 * 12-Jul-2004 (Jens Collin <jens.collin@lakhei.com>) 130 * Added ShellExecute call when all else fails to be able to "launch" any file. 131 * 132 * 02-Apr-2005 (Magnus Olsen <magnus@greatlord.com>) 133 * Remove all hardcode string to En.rc 134 * 135 * 06-May-2005 (Klemens Friedl <frik85@gmail.com>) 136 * Add 'help' command (list all commands plus description) 137 * 138 * 06-jul-2005 (Magnus Olsen <magnus@greatlord.com>) 139 * translate '%errorlevel%' to the internal value. 140 * Add proper memory alloc ProcessInput, the error 141 * handling for memory handling need to be improve 142 */ 143 144 #include "precomp.h" 145 #include <reactos/buildno.h> 146 #include <reactos/version.h> 147 148 typedef NTSTATUS (WINAPI *NtQueryInformationProcessProc)(HANDLE, PROCESSINFOCLASS, 149 PVOID, ULONG, PULONG); 150 typedef NTSTATUS (WINAPI *NtReadVirtualMemoryProc)(HANDLE, PVOID, PVOID, SIZE_T, PSIZE_T); 151 152 BOOL bExit = FALSE; /* Indicates EXIT was typed */ 153 BOOL bCanExit = TRUE; /* Indicates if this shell is exitable */ 154 BOOL bCtrlBreak = FALSE; /* Ctrl-Break or Ctrl-C hit */ 155 BOOL bIgnoreEcho = FALSE; /* Set this to TRUE to prevent a newline, when executing a command */ 156 static BOOL fSingleCommand = 0; /* When we are executing something passed on the command line after /C or /K */ 157 static BOOL bAlwaysStrip = FALSE; 158 INT nErrorLevel = 0; /* Errorlevel of last launched external program */ 159 CRITICAL_SECTION ChildProcessRunningLock; 160 BOOL bDisableBatchEcho = FALSE; 161 BOOL bEnableExtensions = TRUE; 162 BOOL bDelayedExpansion = FALSE; 163 DWORD dwChildProcessId = 0; 164 LPTSTR lpOriginalEnvironment; 165 HANDLE CMD_ModuleHandle; 166 167 BOOL bTitleSet = FALSE; /* Indicates whether the console title has been changed and needs to be restored later */ 168 TCHAR szCurTitle[MAX_PATH]; 169 170 static NtQueryInformationProcessProc NtQueryInformationProcessPtr = NULL; 171 static NtReadVirtualMemoryProc NtReadVirtualMemoryPtr = NULL; 172 173 /* 174 * Default output file stream translation mode is UTF8, but CMD switches 175 * allow to change it to either UTF16 (/U) or ANSI (/A). 176 */ 177 CON_STREAM_MODE OutputStreamMode = UTF8Text; // AnsiText; 178 179 #ifdef INCLUDE_CMD_COLOR 180 WORD wDefColor = 0; /* Default color */ 181 #endif 182 183 /* 184 * convert 185 * 186 * insert commas into a number 187 */ 188 INT 189 ConvertULargeInteger(ULONGLONG num, LPTSTR des, UINT len, BOOL bPutSeparator) 190 { 191 TCHAR temp[39]; /* maximum length with nNumberGroups == 1 */ 192 UINT n, iTarget; 193 194 if (len <= 1) 195 return 0; 196 197 n = 0; 198 iTarget = nNumberGroups; 199 if (!nNumberGroups) 200 bPutSeparator = FALSE; 201 202 do 203 { 204 if (iTarget == n && bPutSeparator) 205 { 206 iTarget += nNumberGroups + 1; 207 temp[38 - n++] = cThousandSeparator; 208 } 209 temp[38 - n++] = (TCHAR)(num % 10) + _T('0'); 210 num /= 10; 211 } while (num > 0); 212 if (n > len-1) 213 n = len-1; 214 215 memcpy(des, temp + 39 - n, n * sizeof(TCHAR)); 216 des[n] = _T('\0'); 217 218 return n; 219 } 220 221 /* 222 * Is a process a console process? 223 */ 224 static BOOL IsConsoleProcess(HANDLE Process) 225 { 226 NTSTATUS Status; 227 PROCESS_BASIC_INFORMATION Info; 228 PEB ProcessPeb; 229 SIZE_T BytesRead; 230 231 if (NULL == NtQueryInformationProcessPtr || NULL == NtReadVirtualMemoryPtr) 232 { 233 return TRUE; 234 } 235 236 Status = NtQueryInformationProcessPtr ( 237 Process, ProcessBasicInformation, 238 &Info, sizeof(PROCESS_BASIC_INFORMATION), NULL); 239 if (! NT_SUCCESS(Status)) 240 { 241 WARN ("NtQueryInformationProcess failed with status %08x\n", Status); 242 return TRUE; 243 } 244 Status = NtReadVirtualMemoryPtr ( 245 Process, Info.PebBaseAddress, &ProcessPeb, 246 sizeof(PEB), &BytesRead); 247 if (! NT_SUCCESS(Status) || sizeof(PEB) != BytesRead) 248 { 249 WARN ("Couldn't read virt mem status %08x bytes read %Iu\n", Status, BytesRead); 250 return TRUE; 251 } 252 253 return IMAGE_SUBSYSTEM_WINDOWS_CUI == ProcessPeb.ImageSubsystem; 254 } 255 256 257 258 #ifdef _UNICODE 259 #define SHELLEXECUTETEXT "ShellExecuteExW" 260 #else 261 #define SHELLEXECUTETEXT "ShellExecuteExA" 262 #endif 263 264 typedef BOOL (WINAPI *MYEX)(LPSHELLEXECUTEINFO lpExecInfo); 265 266 HANDLE RunFile(DWORD flags, LPTSTR filename, LPTSTR params, 267 LPTSTR directory, INT show) 268 { 269 SHELLEXECUTEINFO sei; 270 HMODULE hShell32; 271 MYEX hShExt; 272 BOOL ret; 273 274 TRACE ("RunFile(%s)\n", debugstr_aw(filename)); 275 hShell32 = LoadLibrary(_T("SHELL32.DLL")); 276 if (!hShell32) 277 { 278 WARN ("RunFile: couldn't load SHELL32.DLL!\n"); 279 return NULL; 280 } 281 282 hShExt = (MYEX)(FARPROC)GetProcAddress(hShell32, SHELLEXECUTETEXT); 283 if (!hShExt) 284 { 285 WARN ("RunFile: couldn't find ShellExecuteExA/W in SHELL32.DLL!\n"); 286 FreeLibrary(hShell32); 287 return NULL; 288 } 289 290 TRACE ("RunFile: ShellExecuteExA/W is at %x\n", hShExt); 291 292 memset(&sei, 0, sizeof sei); 293 sei.cbSize = sizeof sei; 294 sei.fMask = flags; 295 sei.lpFile = filename; 296 sei.lpParameters = params; 297 sei.lpDirectory = directory; 298 sei.nShow = show; 299 ret = hShExt(&sei); 300 301 TRACE ("RunFile: ShellExecuteExA/W returned 0x%p\n", ret); 302 303 FreeLibrary(hShell32); 304 return ret ? sei.hProcess : NULL; 305 } 306 307 308 static VOID 309 SetConTitle(LPCTSTR pszTitle) 310 { 311 TCHAR szNewTitle[MAX_PATH]; 312 313 if (!pszTitle) 314 return; 315 316 /* Don't do anything if we run inside a batch file, or we are just running a single command */ 317 if (bc || (fSingleCommand == 1)) 318 return; 319 320 /* Save the original console title and build a new one */ 321 GetConsoleTitle(szCurTitle, ARRAYSIZE(szCurTitle)); 322 StringCchPrintf(szNewTitle, ARRAYSIZE(szNewTitle), 323 _T("%s - %s"), szCurTitle, pszTitle); 324 bTitleSet = TRUE; 325 ConSetTitle(szNewTitle); 326 } 327 328 static VOID 329 ResetConTitle(VOID) 330 { 331 /* Restore the original console title */ 332 if (!bc && bTitleSet) 333 { 334 ConSetTitle(szCurTitle); 335 bTitleSet = FALSE; 336 } 337 } 338 339 /* 340 * This command (in First) was not found in the command table 341 * 342 * Full - output buffer to hold whole command line 343 * First - first word on command line 344 * Rest - rest of command line 345 */ 346 static INT 347 Execute(LPTSTR Full, LPTSTR First, LPTSTR Rest, PARSED_COMMAND *Cmd) 348 { 349 TCHAR *first, *rest, *dot; 350 DWORD dwExitCode = 0; 351 TCHAR *FirstEnd; 352 TCHAR szFullName[MAX_PATH]; 353 TCHAR szFullCmdLine[CMDLINE_LENGTH]; 354 355 TRACE ("Execute: \'%s\' \'%s\'\n", debugstr_aw(First), debugstr_aw(Rest)); 356 357 /* Though it was already parsed once, we have a different set of rules 358 for parsing before we pass to CreateProcess */ 359 if (First[0] == _T('/') || (First[0] && First[1] == _T(':'))) 360 { 361 /* Use the entire first word as the program name (no change) */ 362 FirstEnd = First + _tcslen(First); 363 } 364 else 365 { 366 /* If present in the first word, spaces and ,;=/ end the program 367 * name and become the beginning of its parameters. */ 368 BOOL bInside = FALSE; 369 for (FirstEnd = First; *FirstEnd; FirstEnd++) 370 { 371 if (!bInside && (_istspace(*FirstEnd) || _tcschr(_T(",;=/"), *FirstEnd))) 372 break; 373 bInside ^= *FirstEnd == _T('"'); 374 } 375 } 376 377 /* Copy the new first/rest into the buffer */ 378 rest = &Full[FirstEnd - First + 1]; 379 _tcscpy(rest, FirstEnd); 380 _tcscat(rest, Rest); 381 first = Full; 382 *FirstEnd = _T('\0'); 383 _tcscpy(first, First); 384 385 /* check for a drive change */ 386 if ((_istalpha (first[0])) && (!_tcscmp (first + 1, _T(":")))) 387 { 388 BOOL working = TRUE; 389 if (!SetCurrentDirectory(first)) 390 { 391 /* Guess they changed disc or something, handle that gracefully and get to root */ 392 TCHAR str[4]; 393 str[0]=first[0]; 394 str[1]=_T(':'); 395 str[2]=_T('\\'); 396 str[3]=0; 397 working = SetCurrentDirectory(str); 398 } 399 400 if (!working) ConErrResPuts (STRING_FREE_ERROR1); 401 return !working; 402 } 403 404 /* get the PATH environment variable and parse it */ 405 /* search the PATH environment variable for the binary */ 406 StripQuotes(First); 407 if (!SearchForExecutable(First, szFullName)) 408 { 409 error_bad_command(first); 410 return 1; 411 } 412 413 /* Set the new console title */ 414 FirstEnd = first + (FirstEnd - First); /* Point to the separating NULL in the full built string */ 415 *FirstEnd = _T(' '); 416 SetConTitle(Full); 417 418 /* check if this is a .BAT or .CMD file */ 419 dot = _tcsrchr (szFullName, _T('.')); 420 if (dot && (!_tcsicmp (dot, _T(".bat")) || !_tcsicmp (dot, _T(".cmd")))) 421 { 422 while (*rest == _T(' ')) 423 rest++; 424 425 *FirstEnd = _T('\0'); 426 TRACE ("[BATCH: %s %s]\n", debugstr_aw(szFullName), debugstr_aw(rest)); 427 dwExitCode = Batch(szFullName, first, rest, Cmd); 428 } 429 else 430 { 431 /* exec the program */ 432 PROCESS_INFORMATION prci; 433 STARTUPINFO stui; 434 435 /* build command line for CreateProcess(): FullName + " " + rest */ 436 BOOL quoted = !!_tcschr(First, _T(' ')); 437 _tcscpy(szFullCmdLine, quoted ? _T("\"") : _T("")); 438 _tcsncat(szFullCmdLine, First, CMDLINE_LENGTH - _tcslen(szFullCmdLine) - 1); 439 _tcsncat(szFullCmdLine, quoted ? _T("\"") : _T(""), CMDLINE_LENGTH - _tcslen(szFullCmdLine) - 1); 440 441 if (*rest) 442 { 443 _tcsncat(szFullCmdLine, _T(" "), CMDLINE_LENGTH - _tcslen(szFullCmdLine) - 1); 444 _tcsncat(szFullCmdLine, rest, CMDLINE_LENGTH - _tcslen(szFullCmdLine) - 1); 445 } 446 447 TRACE ("[EXEC: %s]\n", debugstr_aw(szFullCmdLine)); 448 449 /* fill startup info */ 450 memset(&stui, 0, sizeof(stui)); 451 stui.cb = sizeof(stui); 452 stui.lpTitle = Full; 453 stui.dwFlags = STARTF_USESHOWWINDOW; 454 stui.wShowWindow = SW_SHOWDEFAULT; 455 456 /* Set the console to standard mode */ 457 SetConsoleMode(ConStreamGetOSHandle(StdIn), 458 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); 459 460 if (CreateProcess(szFullName, 461 szFullCmdLine, 462 NULL, 463 NULL, 464 TRUE, 465 0, 466 NULL, 467 NULL, 468 &stui, 469 &prci)) 470 { 471 CloseHandle(prci.hThread); 472 } 473 else 474 { 475 // See if we can run this with ShellExecute() ie myfile.xls 476 prci.hProcess = RunFile(SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE, 477 szFullName, 478 rest, 479 NULL, 480 SW_SHOWNORMAL); 481 } 482 483 *FirstEnd = _T('\0'); 484 485 if (prci.hProcess != NULL) 486 { 487 if (bc != NULL || fSingleCommand != 0 || IsConsoleProcess(prci.hProcess)) 488 { 489 /* when processing a batch file or starting console processes: execute synchronously */ 490 EnterCriticalSection(&ChildProcessRunningLock); 491 dwChildProcessId = prci.dwProcessId; 492 493 WaitForSingleObject(prci.hProcess, INFINITE); 494 495 LeaveCriticalSection(&ChildProcessRunningLock); 496 497 GetExitCodeProcess(prci.hProcess, &dwExitCode); 498 nErrorLevel = (INT)dwExitCode; 499 } 500 CloseHandle(prci.hProcess); 501 } 502 else 503 { 504 TRACE ("[ShellExecute failed!: %s]\n", debugstr_aw(Full)); 505 error_bad_command(first); 506 dwExitCode = 1; 507 } 508 509 /* Restore our default console mode */ 510 SetConsoleMode(ConStreamGetOSHandle(StdIn), 511 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); 512 SetConsoleMode(ConStreamGetOSHandle(StdOut), 513 ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT); 514 } 515 516 /* Update our local codepage cache */ 517 { 518 UINT uNewInputCodePage = GetConsoleCP(); 519 UINT uNewOutputCodePage = GetConsoleOutputCP(); 520 521 if ((InputCodePage != uNewInputCodePage) || 522 (OutputCodePage != uNewOutputCodePage)) 523 { 524 /* Update the locale as well */ 525 InitLocale(); 526 } 527 528 InputCodePage = uNewInputCodePage; 529 OutputCodePage = uNewOutputCodePage; 530 531 /* Update the streams codepage cache as well */ 532 ConStreamSetCacheCodePage(StdIn , InputCodePage ); 533 ConStreamSetCacheCodePage(StdOut, OutputCodePage); 534 ConStreamSetCacheCodePage(StdErr, OutputCodePage); 535 } 536 537 /* Restore the original console title */ 538 ResetConTitle(); 539 540 return dwExitCode; 541 } 542 543 544 /* 545 * Look through the internal commands and determine whether or not this 546 * command is one of them. If it is, call the command. If not, call 547 * execute to run it as an external program. 548 * 549 * first - first word on command line 550 * rest - rest of command line 551 */ 552 INT 553 DoCommand(LPTSTR first, LPTSTR rest, PARSED_COMMAND *Cmd) 554 { 555 TCHAR *com; 556 TCHAR *cp; 557 LPTSTR param; /* Pointer to command's parameters */ 558 INT cl; 559 LPCOMMAND cmdptr; 560 BOOL nointernal = FALSE; 561 INT ret; 562 563 TRACE ("DoCommand: (\'%s\' \'%s\')\n", debugstr_aw(first), debugstr_aw(rest)); 564 565 /* Full command line */ 566 com = cmd_alloc((_tcslen(first) + _tcslen(rest) + 2) * sizeof(TCHAR)); 567 if (com == NULL) 568 { 569 error_out_of_memory(); 570 return 1; 571 } 572 573 /* If present in the first word, these characters end the name of an 574 * internal command and become the beginning of its parameters. */ 575 cp = first + _tcscspn(first, _T("\t +,/;=[]")); 576 577 for (cl = 0; cl < (cp - first); cl++) 578 { 579 /* These characters do it too, but if one of them is present, 580 * then we check to see if the word is a file name and skip 581 * checking for internal commands if so. 582 * This allows running programs with names like "echo.exe" */ 583 if (_tcschr(_T(".:\\"), first[cl])) 584 { 585 TCHAR tmp = *cp; 586 *cp = _T('\0'); 587 nointernal = IsExistingFile(first); 588 *cp = tmp; 589 break; 590 } 591 } 592 593 /* Scan internal command table */ 594 for (cmdptr = cmds; !nointernal && cmdptr->name; cmdptr++) 595 { 596 if (!_tcsnicmp(first, cmdptr->name, cl) && cmdptr->name[cl] == _T('\0')) 597 { 598 _tcscpy(com, first); 599 _tcscat(com, rest); 600 param = &com[cl]; 601 602 /* Skip over whitespace to rest of line, exclude 'echo' command */ 603 if (_tcsicmp(cmdptr->name, _T("echo")) != 0) 604 { 605 while (_istspace(*param)) 606 param++; 607 } 608 609 /* Set the new console title */ 610 SetConTitle(com); 611 612 ret = cmdptr->func(param); 613 614 /* Restore the original console title */ 615 ResetConTitle(); 616 617 cmd_free(com); 618 return ret; 619 } 620 } 621 622 ret = Execute(com, first, rest, Cmd); 623 cmd_free(com); 624 return ret; 625 } 626 627 628 /* 629 * process the command line and execute the appropriate functions 630 * full input/output redirection and piping are supported 631 */ 632 INT ParseCommandLine(LPTSTR cmd) 633 { 634 INT Ret = 0; 635 PARSED_COMMAND *Cmd; 636 637 Cmd = ParseCommand(cmd); 638 if (!Cmd) 639 { 640 /* Return an adequate error code */ 641 return (!bParseError ? 0 : 1); 642 } 643 644 Ret = ExecuteCommand(Cmd); 645 FreeCommand(Cmd); 646 return Ret; 647 } 648 649 /* Execute a command without waiting for it to finish. If it's an internal 650 * command or batch file, we must create a new cmd.exe process to handle it. 651 * TODO: For now, this just always creates a cmd.exe process. 652 * This works, but is inefficient for running external programs, 653 * which could just be run directly. */ 654 static HANDLE 655 ExecuteAsync(PARSED_COMMAND *Cmd) 656 { 657 TCHAR CmdPath[MAX_PATH]; 658 TCHAR CmdParams[CMDLINE_LENGTH], *ParamsEnd; 659 STARTUPINFO stui; 660 PROCESS_INFORMATION prci; 661 662 /* Get the path to cmd.exe */ 663 GetModuleFileName(NULL, CmdPath, ARRAYSIZE(CmdPath)); 664 665 /* Build the parameter string to pass to cmd.exe */ 666 ParamsEnd = _stpcpy(CmdParams, _T("/S/D/C\"")); 667 ParamsEnd = Unparse(Cmd, ParamsEnd, &CmdParams[CMDLINE_LENGTH - 2]); 668 if (!ParamsEnd) 669 { 670 error_out_of_memory(); 671 return NULL; 672 } 673 _tcscpy(ParamsEnd, _T("\"")); 674 675 memset(&stui, 0, sizeof stui); 676 stui.cb = sizeof(STARTUPINFO); 677 if (!CreateProcess(CmdPath, CmdParams, NULL, NULL, TRUE, 0, 678 NULL, NULL, &stui, &prci)) 679 { 680 ErrorMessage(GetLastError(), NULL); 681 return NULL; 682 } 683 684 CloseHandle(prci.hThread); 685 return prci.hProcess; 686 } 687 688 static INT 689 ExecutePipeline(PARSED_COMMAND *Cmd) 690 { 691 #ifdef FEATURE_REDIRECTION 692 HANDLE hInput = NULL; 693 HANDLE hOldConIn = GetStdHandle(STD_INPUT_HANDLE); 694 HANDLE hOldConOut = GetStdHandle(STD_OUTPUT_HANDLE); 695 HANDLE hProcess[MAXIMUM_WAIT_OBJECTS]; 696 INT nProcesses = 0; 697 DWORD dwExitCode; 698 699 /* Do all but the last pipe command */ 700 do 701 { 702 HANDLE hPipeRead, hPipeWrite; 703 if (nProcesses > (MAXIMUM_WAIT_OBJECTS - 2)) 704 { 705 error_too_many_parameters(_T("|")); 706 goto failed; 707 } 708 709 /* Create the pipe that this process will write into. 710 * Make the handles non-inheritable initially, because this 711 * process shouldn't inherit the reading handle. */ 712 if (!CreatePipe(&hPipeRead, &hPipeWrite, NULL, 0)) 713 { 714 error_no_pipe(); 715 goto failed; 716 } 717 718 /* The writing side of the pipe is STDOUT for this process */ 719 SetHandleInformation(hPipeWrite, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); 720 SetStdHandle(STD_OUTPUT_HANDLE, hPipeWrite); 721 722 /* Execute it (error check is done later for easier cleanup) */ 723 hProcess[nProcesses] = ExecuteAsync(Cmd->Subcommands); 724 CloseHandle(hPipeWrite); 725 if (hInput) 726 CloseHandle(hInput); 727 728 /* The reading side of the pipe will be STDIN for the next process */ 729 SetHandleInformation(hPipeRead, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); 730 SetStdHandle(STD_INPUT_HANDLE, hPipeRead); 731 hInput = hPipeRead; 732 733 if (!hProcess[nProcesses]) 734 goto failed; 735 nProcesses++; 736 737 Cmd = Cmd->Subcommands->Next; 738 } while (Cmd->Type == C_PIPE); 739 740 /* The last process uses the original STDOUT */ 741 SetStdHandle(STD_OUTPUT_HANDLE, hOldConOut); 742 hProcess[nProcesses] = ExecuteAsync(Cmd); 743 if (!hProcess[nProcesses]) 744 goto failed; 745 nProcesses++; 746 CloseHandle(hInput); 747 SetStdHandle(STD_INPUT_HANDLE, hOldConIn); 748 749 /* Wait for all processes to complete */ 750 EnterCriticalSection(&ChildProcessRunningLock); 751 WaitForMultipleObjects(nProcesses, hProcess, TRUE, INFINITE); 752 LeaveCriticalSection(&ChildProcessRunningLock); 753 754 /* Use the exit code of the last process in the pipeline */ 755 GetExitCodeProcess(hProcess[nProcesses - 1], &dwExitCode); 756 nErrorLevel = (INT)dwExitCode; 757 758 while (--nProcesses >= 0) 759 CloseHandle(hProcess[nProcesses]); 760 return nErrorLevel; 761 762 failed: 763 if (hInput) 764 CloseHandle(hInput); 765 while (--nProcesses >= 0) 766 { 767 TerminateProcess(hProcess[nProcesses], 0); 768 CloseHandle(hProcess[nProcesses]); 769 } 770 SetStdHandle(STD_INPUT_HANDLE, hOldConIn); 771 SetStdHandle(STD_OUTPUT_HANDLE, hOldConOut); 772 #endif 773 774 return nErrorLevel; 775 } 776 777 INT 778 ExecuteCommand( 779 IN PARSED_COMMAND *Cmd) 780 { 781 #define SeenGoto() \ 782 (bc && bc->current == NULL) 783 784 PARSED_COMMAND *Sub; 785 LPTSTR First, Rest; 786 INT Ret = 0; 787 788 /* 789 * Do not execute any command if we are about to exit CMD, or about to 790 * change batch execution context, e.g. in case of a CALL / GOTO / EXIT. 791 */ 792 if (!Cmd) 793 return 0; 794 if (bExit || SeenGoto()) 795 return 0; 796 797 if (!PerformRedirection(Cmd->Redirections)) 798 return 1; 799 800 switch (Cmd->Type) 801 { 802 case C_COMMAND: 803 Ret = 1; 804 First = DoDelayedExpansion(Cmd->Command.First); 805 if (First) 806 { 807 Rest = DoDelayedExpansion(Cmd->Command.Rest); 808 if (Rest) 809 { 810 Ret = DoCommand(First, Rest, Cmd); 811 cmd_free(Rest); 812 } 813 cmd_free(First); 814 } 815 break; 816 817 case C_QUIET: 818 case C_BLOCK: 819 case C_MULTI: 820 for (Sub = Cmd->Subcommands; Sub && !SeenGoto(); Sub = Sub->Next) 821 Ret = ExecuteCommand(Sub); 822 break; 823 824 case C_OR: 825 Sub = Cmd->Subcommands; 826 Ret = ExecuteCommand(Sub); 827 if ((Ret != 0) && !SeenGoto()) 828 { 829 nErrorLevel = Ret; 830 Ret = ExecuteCommand(Sub->Next); 831 } 832 break; 833 834 case C_AND: 835 Sub = Cmd->Subcommands; 836 Ret = ExecuteCommand(Sub); 837 if ((Ret == 0) && !SeenGoto()) 838 Ret = ExecuteCommand(Sub->Next); 839 break; 840 841 case C_PIPE: 842 Ret = ExecutePipeline(Cmd); 843 break; 844 845 case C_IF: 846 Ret = ExecuteIf(Cmd); 847 break; 848 849 case C_FOR: 850 Ret = ExecuteFor(Cmd); 851 break; 852 } 853 854 UndoRedirection(Cmd->Redirections, NULL); 855 return Ret; 856 857 #undef SeenGoto 858 } 859 860 INT 861 ExecuteCommandWithEcho( 862 IN PARSED_COMMAND *Cmd) 863 { 864 /* Echo the reconstructed command line */ 865 if (bEcho && !bDisableBatchEcho && Cmd && (Cmd->Type != C_QUIET)) 866 { 867 if (!bIgnoreEcho) 868 ConOutChar(_T('\n')); 869 PrintPrompt(); 870 EchoCommand(Cmd); 871 ConOutChar(_T('\n')); 872 } 873 874 /* Run the command */ 875 return ExecuteCommand(Cmd); 876 } 877 878 LPTSTR 879 GetEnvVar(LPCTSTR varName) 880 { 881 static LPTSTR ret = NULL; 882 UINT size; 883 884 cmd_free(ret); 885 ret = NULL; 886 size = GetEnvironmentVariable(varName, NULL, 0); 887 if (size > 0) 888 { 889 ret = cmd_alloc(size * sizeof(TCHAR)); 890 if (ret != NULL) 891 GetEnvironmentVariable(varName, ret, size + 1); 892 } 893 return ret; 894 } 895 896 LPCTSTR 897 GetEnvVarOrSpecial(LPCTSTR varName) 898 { 899 static TCHAR ret[MAX_PATH]; 900 901 LPTSTR var = GetEnvVar(varName); 902 if (var) 903 return var; 904 905 /* The environment variable doesn't exist, look for 906 * a "special" one only if extensions are enabled. */ 907 if (!bEnableExtensions) 908 return NULL; 909 910 /* %CD% */ 911 if (_tcsicmp(varName, _T("CD")) == 0) 912 { 913 GetCurrentDirectory(ARRAYSIZE(ret), ret); 914 return ret; 915 } 916 /* %DATE% */ 917 else if (_tcsicmp(varName, _T("DATE")) == 0) 918 { 919 return GetDateString(); 920 } 921 /* %TIME% */ 922 else if (_tcsicmp(varName, _T("TIME")) == 0) 923 { 924 return GetTimeString(); 925 } 926 /* %RANDOM% */ 927 else if (_tcsicmp(varName, _T("RANDOM")) == 0) 928 { 929 /* Get random number */ 930 _itot(rand(), ret, 10); 931 return ret; 932 } 933 /* %CMDCMDLINE% */ 934 else if (_tcsicmp(varName, _T("CMDCMDLINE")) == 0) 935 { 936 return GetCommandLine(); 937 } 938 /* %CMDEXTVERSION% */ 939 else if (_tcsicmp(varName, _T("CMDEXTVERSION")) == 0) 940 { 941 /* Set Command Extensions version number to CMDEXTVERSION */ 942 _itot(CMDEXTVERSION, ret, 10); 943 return ret; 944 } 945 /* %ERRORLEVEL% */ 946 else if (_tcsicmp(varName, _T("ERRORLEVEL")) == 0) 947 { 948 _itot(nErrorLevel, ret, 10); 949 return ret; 950 } 951 #if (NTDDI_VERSION >= NTDDI_WIN7) 952 /* Available in Win7+, even if the underlying API is available in Win2003+ */ 953 /* %HIGHESTNUMANODENUMBER% */ 954 else if (_tcsicmp(varName, _T("HIGHESTNUMANODENUMBER")) == 0) 955 { 956 ULONG NumaNodeNumber = 0; 957 GetNumaHighestNodeNumber(&NumaNodeNumber); 958 _itot(NumaNodeNumber, ret, 10); 959 return ret; 960 } 961 #endif 962 963 return NULL; 964 } 965 966 /* Handle the %~var syntax */ 967 static LPTSTR 968 GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, BOOL *)) 969 { 970 static const TCHAR ModifierTable[] = _T("dpnxfsatz"); 971 enum { 972 M_DRIVE = 1, /* D: drive letter */ 973 M_PATH = 2, /* P: path */ 974 M_NAME = 4, /* N: filename */ 975 M_EXT = 8, /* X: extension */ 976 M_FULL = 16, /* F: full path (drive+path+name+ext) */ 977 M_SHORT = 32, /* S: full path (drive+path+name+ext), use short names */ 978 M_ATTR = 64, /* A: attributes */ 979 M_TIME = 128, /* T: modification time */ 980 M_SIZE = 256, /* Z: file size */ 981 } Modifiers = 0; 982 983 TCHAR *Format, *FormatEnd; 984 TCHAR *PathVarName = NULL; 985 LPTSTR Variable; 986 TCHAR *VarEnd; 987 BOOL VariableIsParam0; 988 TCHAR FullPath[MAX_PATH]; 989 TCHAR FixedPath[MAX_PATH], *Filename, *Extension; 990 HANDLE hFind; 991 WIN32_FIND_DATA w32fd; 992 TCHAR *In, *Out; 993 994 static TCHAR Result[CMDLINE_LENGTH]; 995 996 /* There is ambiguity between modifier characters and FOR variables; 997 * the rule that cmd uses is to pick the longest possible match. 998 * For example, if there is a %n variable, then out of %~anxnd, 999 * %~anxn will be substituted rather than just %~an. */ 1000 1001 /* First, go through as many modifier characters as possible */ 1002 FormatEnd = Format = *pFormat; 1003 while (*FormatEnd && _tcschr(ModifierTable, _totlower(*FormatEnd))) 1004 FormatEnd++; 1005 1006 if (*FormatEnd == _T('$')) 1007 { 1008 /* $PATH: syntax */ 1009 PathVarName = FormatEnd + 1; 1010 FormatEnd = _tcschr(PathVarName, _T(':')); 1011 if (!FormatEnd) 1012 return NULL; 1013 1014 /* Must be immediately followed by the variable */ 1015 Variable = GetVar(*++FormatEnd, &VariableIsParam0); 1016 if (!Variable) 1017 return NULL; 1018 } 1019 else 1020 { 1021 /* Backtrack if necessary to get a variable name match */ 1022 while (!(Variable = GetVar(*FormatEnd, &VariableIsParam0))) 1023 { 1024 if (FormatEnd == Format) 1025 return NULL; 1026 FormatEnd--; 1027 } 1028 } 1029 1030 for (; Format < FormatEnd && *Format != _T('$'); Format++) 1031 Modifiers |= 1 << (_tcschr(ModifierTable, _totlower(*Format)) - ModifierTable); 1032 1033 *pFormat = FormatEnd + 1; 1034 1035 /* Exclude the leading and trailing quotes */ 1036 VarEnd = &Variable[_tcslen(Variable)]; 1037 if (*Variable == _T('"')) 1038 { 1039 Variable++; 1040 if (VarEnd > Variable && VarEnd[-1] == _T('"')) 1041 VarEnd--; 1042 } 1043 1044 if ((char *)VarEnd - (char *)Variable >= sizeof Result) 1045 return _T(""); 1046 memcpy(Result, Variable, (char *)VarEnd - (char *)Variable); 1047 Result[VarEnd - Variable] = _T('\0'); 1048 1049 if (PathVarName) 1050 { 1051 /* $PATH: syntax - search the directories listed in the 1052 * specified environment variable for the file */ 1053 LPTSTR PathVar; 1054 FormatEnd[-1] = _T('\0'); 1055 PathVar = GetEnvVar(PathVarName); 1056 FormatEnd[-1] = _T(':'); 1057 if (!PathVar || 1058 !SearchPath(PathVar, Result, NULL, ARRAYSIZE(FullPath), FullPath, NULL)) 1059 { 1060 return _T(""); 1061 } 1062 } 1063 else if (Modifiers == 0) 1064 { 1065 /* For plain %~var with no modifiers, just return the variable without quotes */ 1066 return Result; 1067 } 1068 else if (VariableIsParam0) 1069 { 1070 /* Special case: If the variable is %0 and modifier characters are present, 1071 * use the batch file's path (which includes the .bat/.cmd extension) 1072 * rather than the actual %0 variable (which might not). */ 1073 _tcscpy(FullPath, bc->BatchFilePath); 1074 } 1075 else 1076 { 1077 /* Convert the variable, now without quotes, to a full path */ 1078 if (!GetFullPathName(Result, ARRAYSIZE(FullPath), FullPath, NULL)) 1079 return _T(""); 1080 } 1081 1082 /* Next step is to change the path to fix letter case (e.g. 1083 * C:\ReAcToS -> C:\ReactOS) and, if requested with the S modifier, 1084 * replace long filenames with short. */ 1085 1086 In = FullPath; 1087 Out = FixedPath; 1088 1089 /* Copy drive letter */ 1090 *Out++ = *In++; 1091 *Out++ = *In++; 1092 *Out++ = *In++; 1093 /* Loop over each \-separated component in the path */ 1094 do { 1095 TCHAR *Next = _tcschr(In, _T('\\')); 1096 if (Next) 1097 *Next++ = _T('\0'); 1098 /* Use FindFirstFile to get the correct name */ 1099 if (Out + _tcslen(In) + 1 >= &FixedPath[ARRAYSIZE(FixedPath)]) 1100 return _T(""); 1101 _tcscpy(Out, In); 1102 hFind = FindFirstFile(FixedPath, &w32fd); 1103 /* If it doesn't exist, just leave the name as it was given */ 1104 if (hFind != INVALID_HANDLE_VALUE) 1105 { 1106 LPTSTR FixedComponent = w32fd.cFileName; 1107 if (*w32fd.cAlternateFileName && 1108 ((Modifiers & M_SHORT) || !_tcsicmp(In, w32fd.cAlternateFileName))) 1109 { 1110 FixedComponent = w32fd.cAlternateFileName; 1111 } 1112 FindClose(hFind); 1113 1114 if (Out + _tcslen(FixedComponent) + 1 >= &FixedPath[ARRAYSIZE(FixedPath)]) 1115 return _T(""); 1116 _tcscpy(Out, FixedComponent); 1117 } 1118 Filename = Out; 1119 Out += _tcslen(Out); 1120 *Out++ = _T('\\'); 1121 1122 In = Next; 1123 } while (In != NULL); 1124 Out[-1] = _T('\0'); 1125 1126 /* Build the result string. Start with attributes, modification time, and 1127 * file size. If the file didn't exist, these fields will all be empty. */ 1128 Out = Result; 1129 if (hFind != INVALID_HANDLE_VALUE) 1130 { 1131 if (Modifiers & M_ATTR) 1132 { 1133 static const struct { 1134 TCHAR Character; 1135 WORD Value; 1136 } *Attrib, Table[] = { 1137 { _T('d'), FILE_ATTRIBUTE_DIRECTORY }, 1138 { _T('r'), FILE_ATTRIBUTE_READONLY }, 1139 { _T('a'), FILE_ATTRIBUTE_ARCHIVE }, 1140 { _T('h'), FILE_ATTRIBUTE_HIDDEN }, 1141 { _T('s'), FILE_ATTRIBUTE_SYSTEM }, 1142 { _T('c'), FILE_ATTRIBUTE_COMPRESSED }, 1143 { _T('o'), FILE_ATTRIBUTE_OFFLINE }, 1144 { _T('t'), FILE_ATTRIBUTE_TEMPORARY }, 1145 { _T('l'), FILE_ATTRIBUTE_REPARSE_POINT }, 1146 #if (NTDDI_VERSION >= NTDDI_WIN8) 1147 { _T('v'), FILE_ATTRIBUTE_INTEGRITY_STREAM }, 1148 { _T('x'), FILE_ATTRIBUTE_NO_SCRUB_DATA /* 0x20000 */ }, 1149 #endif 1150 }; 1151 for (Attrib = Table; Attrib != &Table[ARRAYSIZE(Table)]; Attrib++) 1152 { 1153 *Out++ = w32fd.dwFileAttributes & Attrib->Value 1154 ? Attrib->Character 1155 : _T('-'); 1156 } 1157 *Out++ = _T(' '); 1158 } 1159 if (Modifiers & M_TIME) 1160 { 1161 FILETIME ft; 1162 SYSTEMTIME st; 1163 FileTimeToLocalFileTime(&w32fd.ftLastWriteTime, &ft); 1164 FileTimeToSystemTime(&ft, &st); 1165 1166 Out += FormatDate(Out, &st, TRUE); 1167 *Out++ = _T(' '); 1168 Out += FormatTime(Out, &st); 1169 *Out++ = _T(' '); 1170 } 1171 if (Modifiers & M_SIZE) 1172 { 1173 ULARGE_INTEGER Size; 1174 Size.LowPart = w32fd.nFileSizeLow; 1175 Size.HighPart = w32fd.nFileSizeHigh; 1176 Out += _stprintf(Out, _T("%I64u "), Size.QuadPart); 1177 } 1178 } 1179 1180 /* When using the path-searching syntax or the S modifier, 1181 * at least part of the file path is always included. 1182 * If none of the DPNX modifiers are present, include the full path */ 1183 if (PathVarName || (Modifiers & M_SHORT)) 1184 if ((Modifiers & (M_DRIVE | M_PATH | M_NAME | M_EXT)) == 0) 1185 Modifiers |= M_FULL; 1186 1187 /* Now add the requested parts of the name. 1188 * With the F modifier, add all parts to form the full path. */ 1189 Extension = _tcsrchr(Filename, _T('.')); 1190 if (Modifiers & (M_DRIVE | M_FULL)) 1191 { 1192 *Out++ = FixedPath[0]; 1193 *Out++ = FixedPath[1]; 1194 } 1195 if (Modifiers & (M_PATH | M_FULL)) 1196 { 1197 memcpy(Out, &FixedPath[2], (char *)Filename - (char *)&FixedPath[2]); 1198 Out += Filename - &FixedPath[2]; 1199 } 1200 if (Modifiers & (M_NAME | M_FULL)) 1201 { 1202 while (*Filename && Filename != Extension) 1203 *Out++ = *Filename++; 1204 } 1205 if (Modifiers & (M_EXT | M_FULL)) 1206 { 1207 if (Extension) 1208 Out = _stpcpy(Out, Extension); 1209 } 1210 1211 /* Trim trailing space which otherwise would appear as a 1212 * result of using the A/T/Z modifiers but no others. */ 1213 while (Out != &Result[0] && Out[-1] == _T(' ')) 1214 Out--; 1215 *Out = _T('\0'); 1216 1217 return Result; 1218 } 1219 1220 static LPCTSTR 1221 GetBatchVar(TCHAR *varName, UINT *varNameLen) 1222 { 1223 LPCTSTR ret; 1224 TCHAR *varNameEnd; 1225 BOOL dummy; 1226 1227 *varNameLen = 1; 1228 1229 switch ( *varName ) 1230 { 1231 case _T('~'): 1232 varNameEnd = varName + 1; 1233 ret = GetEnhancedVar(&varNameEnd, FindArg); 1234 if (!ret) 1235 { 1236 ParseErrorEx(varName); 1237 return NULL; 1238 } 1239 *varNameLen = varNameEnd - varName; 1240 return ret; 1241 1242 case _T('0'): 1243 case _T('1'): 1244 case _T('2'): 1245 case _T('3'): 1246 case _T('4'): 1247 case _T('5'): 1248 case _T('6'): 1249 case _T('7'): 1250 case _T('8'): 1251 case _T('9'): 1252 return FindArg(*varName, &dummy); 1253 1254 case _T('*'): 1255 /* Copy over the raw params (not including the batch file name) */ 1256 return bc->raw_params; 1257 1258 case _T('%'): 1259 return _T("%"); 1260 } 1261 return NULL; 1262 } 1263 1264 BOOL 1265 SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim) 1266 { 1267 #define APPEND(From, Length) { \ 1268 if (Dest + (Length) > DestEnd) \ 1269 goto too_long; \ 1270 memcpy(Dest, From, (Length) * sizeof(TCHAR)); \ 1271 Dest += Length; } 1272 #define APPEND1(Char) { \ 1273 if (Dest >= DestEnd) \ 1274 goto too_long; \ 1275 *Dest++ = Char; } 1276 1277 TCHAR *DestEnd = Dest + CMDLINE_LENGTH - 1; 1278 const TCHAR *Var; 1279 int VarLength; 1280 TCHAR *SubstStart; 1281 TCHAR EndChr; 1282 while (*Src) 1283 { 1284 if (*Src != Delim) 1285 { 1286 APPEND1(*Src++) 1287 continue; 1288 } 1289 1290 Src++; 1291 if (bc && Delim == _T('%')) 1292 { 1293 UINT NameLen; 1294 Var = GetBatchVar(Src, &NameLen); 1295 if (!Var && bParseError) 1296 { 1297 /* Return the partially-parsed command to be 1298 * echoed for error diagnostics purposes. */ 1299 APPEND1(Delim); 1300 APPEND(Src, DestEnd-Dest); 1301 return FALSE; 1302 } 1303 if (Var != NULL) 1304 { 1305 VarLength = _tcslen(Var); 1306 APPEND(Var, VarLength) 1307 Src += NameLen; 1308 continue; 1309 } 1310 } 1311 1312 /* Find the end of the variable name. A colon (:) will usually 1313 * end the name and begin the optional modifier, but not if it 1314 * is immediately followed by the delimiter (%VAR:%). */ 1315 SubstStart = Src; 1316 while (*Src != Delim && !(*Src == _T(':') && Src[1] != Delim)) 1317 { 1318 if (!*Src) 1319 goto bad_subst; 1320 Src++; 1321 } 1322 1323 EndChr = *Src; 1324 *Src = _T('\0'); 1325 Var = GetEnvVarOrSpecial(SubstStart); 1326 *Src++ = EndChr; 1327 if (Var == NULL) 1328 { 1329 /* In a batch file, %NONEXISTENT% "expands" to an empty string */ 1330 if (bc) 1331 continue; 1332 goto bad_subst; 1333 } 1334 VarLength = _tcslen(Var); 1335 1336 if (EndChr == Delim) 1337 { 1338 /* %VAR% - use as-is */ 1339 APPEND(Var, VarLength) 1340 } 1341 else if (*Src == _T('~')) 1342 { 1343 /* %VAR:~[start][,length]% - substring 1344 * Negative values are offsets from the end */ 1345 int Start = _tcstol(Src + 1, &Src, 0); 1346 int End = VarLength; 1347 if (Start < 0) 1348 Start += VarLength; 1349 Start = max(Start, 0); 1350 Start = min(Start, VarLength); 1351 if (*Src == _T(',')) 1352 { 1353 End = _tcstol(Src + 1, &Src, 0); 1354 End += (End < 0) ? VarLength : Start; 1355 End = max(End, Start); 1356 End = min(End, VarLength); 1357 } 1358 if (*Src++ != Delim) 1359 goto bad_subst; 1360 APPEND(&Var[Start], End - Start); 1361 } 1362 else 1363 { 1364 /* %VAR:old=new% - replace all occurrences of old with new 1365 * %VAR:*old=new% - replace first occurrence only, 1366 * and remove everything before it */ 1367 TCHAR *Old, *New; 1368 DWORD OldLength, NewLength; 1369 BOOL Star = FALSE; 1370 int LastMatch = 0, i = 0; 1371 1372 if (*Src == _T('*')) 1373 { 1374 Star = TRUE; 1375 Src++; 1376 } 1377 1378 /* the string to replace may contain the delimiter */ 1379 Src = _tcschr(Old = Src, _T('=')); 1380 if (Src == NULL) 1381 goto bad_subst; 1382 OldLength = Src++ - Old; 1383 if (OldLength == 0) 1384 goto bad_subst; 1385 1386 Src = _tcschr(New = Src, Delim); 1387 if (Src == NULL) 1388 goto bad_subst; 1389 NewLength = Src++ - New; 1390 1391 while (i < VarLength) 1392 { 1393 if (_tcsnicmp(&Var[i], Old, OldLength) == 0) 1394 { 1395 if (!Star) 1396 APPEND(&Var[LastMatch], i - LastMatch) 1397 APPEND(New, NewLength) 1398 i += OldLength; 1399 LastMatch = i; 1400 if (Star) 1401 break; 1402 continue; 1403 } 1404 i++; 1405 } 1406 APPEND(&Var[LastMatch], VarLength - LastMatch) 1407 } 1408 continue; 1409 1410 bad_subst: 1411 Src = SubstStart; 1412 if (!bc) 1413 APPEND1(Delim) 1414 } 1415 *Dest = _T('\0'); 1416 return TRUE; 1417 too_long: 1418 ConOutResPrintf(STRING_ALIAS_ERROR); 1419 nErrorLevel = 9023; 1420 return FALSE; 1421 #undef APPEND 1422 #undef APPEND1 1423 } 1424 1425 /* Search the list of FOR contexts for a variable */ 1426 static LPTSTR 1427 FindForVar(TCHAR Var, BOOL *IsParam0) 1428 { 1429 PFOR_CONTEXT Ctx; 1430 1431 *IsParam0 = FALSE; 1432 for (Ctx = fc; Ctx != NULL; Ctx = Ctx->prev) 1433 { 1434 if ((UINT)(Var - Ctx->firstvar) < Ctx->varcount) 1435 return Ctx->values[Var - Ctx->firstvar]; 1436 } 1437 return NULL; 1438 } 1439 1440 BOOL 1441 SubstituteForVars(TCHAR *Src, TCHAR *Dest) 1442 { 1443 TCHAR *DestEnd = &Dest[CMDLINE_LENGTH - 1]; 1444 while (*Src) 1445 { 1446 if (Src[0] == _T('%')) 1447 { 1448 BOOL Dummy; 1449 LPTSTR End = &Src[2]; 1450 LPTSTR Value = NULL; 1451 1452 if (Src[1] == _T('~')) 1453 Value = GetEnhancedVar(&End, FindForVar); 1454 1455 if (!Value) 1456 Value = FindForVar(Src[1], &Dummy); 1457 1458 if (Value) 1459 { 1460 if (Dest + _tcslen(Value) > DestEnd) 1461 return FALSE; 1462 Dest = _stpcpy(Dest, Value); 1463 Src = End; 1464 continue; 1465 } 1466 } 1467 /* Not a variable; just copy the character */ 1468 if (Dest >= DestEnd) 1469 return FALSE; 1470 *Dest++ = *Src++; 1471 } 1472 *Dest = _T('\0'); 1473 return TRUE; 1474 } 1475 1476 LPTSTR 1477 DoDelayedExpansion(LPTSTR Line) 1478 { 1479 TCHAR Buf1[CMDLINE_LENGTH]; 1480 TCHAR Buf2[CMDLINE_LENGTH]; 1481 1482 /* First, substitute FOR variables */ 1483 if (!SubstituteForVars(Line, Buf1)) 1484 return NULL; 1485 1486 if (!bDelayedExpansion || !_tcschr(Buf1, _T('!'))) 1487 return cmd_dup(Buf1); 1488 1489 /* FIXME: Delayed substitutions actually aren't quite the same as 1490 * immediate substitutions. In particular, it's possible to escape 1491 * the exclamation point using ^. */ 1492 if (!SubstituteVars(Buf1, Buf2, _T('!'))) 1493 return NULL; 1494 return cmd_dup(Buf2); 1495 } 1496 1497 1498 /* 1499 * Do the prompt/input/process loop. 1500 */ 1501 BOOL 1502 ReadLine(TCHAR *commandline, BOOL bMore) 1503 { 1504 TCHAR readline[CMDLINE_LENGTH]; 1505 LPTSTR ip; 1506 1507 /* if no batch input then... */ 1508 if (bc == NULL) 1509 { 1510 if (bMore) 1511 { 1512 ConOutResPrintf(STRING_MORE); 1513 } 1514 else 1515 { 1516 /* JPP 19980807 - if echo off, don't print prompt */ 1517 if (bEcho) 1518 { 1519 if (!bIgnoreEcho) 1520 ConOutChar(_T('\n')); 1521 PrintPrompt(); 1522 } 1523 } 1524 1525 if (!ReadCommand(readline, CMDLINE_LENGTH - 1)) 1526 { 1527 bExit = TRUE; 1528 return FALSE; 1529 } 1530 1531 if (readline[0] == _T('\0')) 1532 ConOutChar(_T('\n')); 1533 1534 if (CheckCtrlBreak(BREAK_INPUT)) 1535 return FALSE; 1536 1537 if (readline[0] == _T('\0')) 1538 return FALSE; 1539 1540 ip = readline; 1541 } 1542 else 1543 { 1544 ip = ReadBatchLine(); 1545 if (!ip) 1546 return FALSE; 1547 } 1548 1549 return SubstituteVars(ip, commandline, _T('%')); 1550 } 1551 1552 static INT 1553 ProcessInput(VOID) 1554 { 1555 INT Ret = 0; 1556 PARSED_COMMAND *Cmd; 1557 1558 while (!bCanExit || !bExit) 1559 { 1560 /* Reset the Ctrl-Break / Ctrl-C state */ 1561 bCtrlBreak = FALSE; 1562 1563 Cmd = ParseCommand(NULL); 1564 if (!Cmd) 1565 continue; 1566 1567 Ret = ExecuteCommand(Cmd); 1568 FreeCommand(Cmd); 1569 } 1570 1571 return Ret; 1572 } 1573 1574 1575 /* 1576 * Control-break handler. 1577 */ 1578 static BOOL 1579 WINAPI 1580 BreakHandler(IN DWORD dwCtrlType) 1581 { 1582 DWORD dwWritten; 1583 INPUT_RECORD rec; 1584 1585 if ((dwCtrlType != CTRL_C_EVENT) && 1586 (dwCtrlType != CTRL_BREAK_EVENT)) 1587 { 1588 return FALSE; 1589 } 1590 1591 if (!TryEnterCriticalSection(&ChildProcessRunningLock)) 1592 { 1593 /* Child process is running and will have received the control event */ 1594 return TRUE; 1595 } 1596 else 1597 { 1598 LeaveCriticalSection(&ChildProcessRunningLock); 1599 } 1600 1601 bCtrlBreak = TRUE; 1602 1603 rec.EventType = KEY_EVENT; 1604 rec.Event.KeyEvent.bKeyDown = TRUE; 1605 rec.Event.KeyEvent.wRepeatCount = 1; 1606 rec.Event.KeyEvent.wVirtualKeyCode = _T('C'); 1607 rec.Event.KeyEvent.wVirtualScanCode = _T('C') - 35; 1608 rec.Event.KeyEvent.uChar.AsciiChar = _T('C'); 1609 rec.Event.KeyEvent.uChar.UnicodeChar = _T('C'); 1610 rec.Event.KeyEvent.dwControlKeyState = RIGHT_CTRL_PRESSED; 1611 1612 WriteConsoleInput(ConStreamGetOSHandle(StdIn), 1613 &rec, 1614 1, 1615 &dwWritten); 1616 1617 /* FIXME: Handle batch files */ 1618 1619 // ConOutPrintf(_T("^C")); 1620 1621 return TRUE; 1622 } 1623 1624 1625 VOID AddBreakHandler(VOID) 1626 { 1627 SetConsoleCtrlHandler(BreakHandler, TRUE); 1628 } 1629 1630 1631 VOID RemoveBreakHandler(VOID) 1632 { 1633 SetConsoleCtrlHandler(BreakHandler, FALSE); 1634 } 1635 1636 1637 /* 1638 * Show commands and options that are available. 1639 */ 1640 #if 0 1641 static VOID 1642 ShowCommands(VOID) 1643 { 1644 /* print command list */ 1645 ConOutResPuts(STRING_CMD_HELP1); 1646 PrintCommandList(); 1647 1648 /* print feature list */ 1649 ConOutResPuts(STRING_CMD_HELP2); 1650 1651 #ifdef FEATURE_ALIASES 1652 ConOutResPuts(STRING_CMD_HELP3); 1653 #endif 1654 #ifdef FEATURE_HISTORY 1655 ConOutResPuts(STRING_CMD_HELP4); 1656 #endif 1657 #ifdef FEATURE_UNIX_FILENAME_COMPLETION 1658 ConOutResPuts(STRING_CMD_HELP5); 1659 #endif 1660 #ifdef FEATURE_DIRECTORY_STACK 1661 ConOutResPuts(STRING_CMD_HELP6); 1662 #endif 1663 #ifdef FEATURE_REDIRECTION 1664 ConOutResPuts(STRING_CMD_HELP7); 1665 #endif 1666 ConOutChar(_T('\n')); 1667 } 1668 #endif 1669 1670 1671 static VOID 1672 LoadRegistrySettings(HKEY hKeyRoot) 1673 { 1674 LONG lRet; 1675 HKEY hKey; 1676 DWORD dwType, len; 1677 /* 1678 * Buffer big enough to hold the string L"4294967295", 1679 * corresponding to the literal 0xFFFFFFFF (MAXULONG) in decimal. 1680 */ 1681 DWORD Buffer[6]; 1682 1683 lRet = RegOpenKeyEx(hKeyRoot, 1684 _T("Software\\Microsoft\\Command Processor"), 1685 0, 1686 KEY_QUERY_VALUE, 1687 &hKey); 1688 if (lRet != ERROR_SUCCESS) 1689 return; 1690 1691 #ifdef INCLUDE_CMD_COLOR 1692 len = sizeof(Buffer); 1693 lRet = RegQueryValueEx(hKey, 1694 _T("DefaultColor"), 1695 NULL, 1696 &dwType, 1697 (LPBYTE)&Buffer, 1698 &len); 1699 if (lRet == ERROR_SUCCESS) 1700 { 1701 /* Overwrite the default attributes */ 1702 if (dwType == REG_DWORD) 1703 wDefColor = (WORD)*(PDWORD)Buffer; 1704 else if (dwType == REG_SZ) 1705 wDefColor = (WORD)_tcstol((PTSTR)Buffer, NULL, 0); 1706 } 1707 // else, use the default attributes retrieved before. 1708 #endif 1709 1710 #if 0 1711 len = sizeof(Buffer); 1712 lRet = RegQueryValueEx(hKey, 1713 _T("DisableUNCCheck"), 1714 NULL, 1715 &dwType, 1716 (LPBYTE)&Buffer, 1717 &len); 1718 if (lRet == ERROR_SUCCESS) 1719 { 1720 /* Overwrite the default setting */ 1721 if (dwType == REG_DWORD) 1722 bDisableUNCCheck = !!*(PDWORD)Buffer; 1723 else if (dwType == REG_SZ) 1724 bDisableUNCCheck = (_ttol((PTSTR)Buffer) == 1); 1725 } 1726 // else, use the default setting set globally. 1727 #endif 1728 1729 len = sizeof(Buffer); 1730 lRet = RegQueryValueEx(hKey, 1731 _T("DelayedExpansion"), 1732 NULL, 1733 &dwType, 1734 (LPBYTE)&Buffer, 1735 &len); 1736 if (lRet == ERROR_SUCCESS) 1737 { 1738 /* Overwrite the default setting */ 1739 if (dwType == REG_DWORD) 1740 bDelayedExpansion = !!*(PDWORD)Buffer; 1741 else if (dwType == REG_SZ) 1742 bDelayedExpansion = (_ttol((PTSTR)Buffer) == 1); 1743 } 1744 // else, use the default setting set globally. 1745 1746 len = sizeof(Buffer); 1747 lRet = RegQueryValueEx(hKey, 1748 _T("EnableExtensions"), 1749 NULL, 1750 &dwType, 1751 (LPBYTE)&Buffer, 1752 &len); 1753 if (lRet == ERROR_SUCCESS) 1754 { 1755 /* Overwrite the default setting */ 1756 if (dwType == REG_DWORD) 1757 bEnableExtensions = !!*(PDWORD)Buffer; 1758 else if (dwType == REG_SZ) 1759 bEnableExtensions = (_ttol((PTSTR)Buffer) == 1); 1760 } 1761 // else, use the default setting set globally. 1762 1763 len = sizeof(Buffer); 1764 lRet = RegQueryValueEx(hKey, 1765 _T("CompletionChar"), 1766 NULL, 1767 &dwType, 1768 (LPBYTE)&Buffer, 1769 &len); 1770 if (lRet == ERROR_SUCCESS) 1771 { 1772 /* Overwrite the default setting */ 1773 if (dwType == REG_DWORD) 1774 AutoCompletionChar = (TCHAR)*(PDWORD)Buffer; 1775 else if (dwType == REG_SZ) 1776 AutoCompletionChar = (TCHAR)_tcstol((PTSTR)Buffer, NULL, 0); 1777 } 1778 // else, use the default setting set globally. 1779 1780 /* Validity check */ 1781 if (IS_COMPLETION_DISABLED(AutoCompletionChar)) 1782 { 1783 /* Disable autocompletion */ 1784 AutoCompletionChar = 0x20; 1785 } 1786 1787 len = sizeof(Buffer); 1788 lRet = RegQueryValueEx(hKey, 1789 _T("PathCompletionChar"), 1790 NULL, 1791 &dwType, 1792 (LPBYTE)&Buffer, 1793 &len); 1794 if (lRet == ERROR_SUCCESS) 1795 { 1796 /* Overwrite the default setting */ 1797 if (dwType == REG_DWORD) 1798 PathCompletionChar = (TCHAR)*(PDWORD)Buffer; 1799 else if (dwType == REG_SZ) 1800 PathCompletionChar = (TCHAR)_tcstol((PTSTR)Buffer, NULL, 0); 1801 } 1802 // else, use the default setting set globally. 1803 1804 /* Validity check */ 1805 if (IS_COMPLETION_DISABLED(PathCompletionChar)) 1806 { 1807 /* Disable autocompletion */ 1808 PathCompletionChar = 0x20; 1809 } 1810 1811 /* Adjust completion chars */ 1812 if (PathCompletionChar >= 0x20 && AutoCompletionChar < 0x20) 1813 PathCompletionChar = AutoCompletionChar; 1814 else if (AutoCompletionChar >= 0x20 && PathCompletionChar < 0x20) 1815 AutoCompletionChar = PathCompletionChar; 1816 1817 RegCloseKey(hKey); 1818 } 1819 1820 static VOID 1821 ExecuteAutoRunFile(HKEY hKeyRoot) 1822 { 1823 LONG lRet; 1824 HKEY hKey; 1825 DWORD dwType, len; 1826 TCHAR AutoRun[2048]; 1827 1828 lRet = RegOpenKeyEx(hKeyRoot, 1829 _T("Software\\Microsoft\\Command Processor"), 1830 0, 1831 KEY_QUERY_VALUE, 1832 &hKey); 1833 if (lRet != ERROR_SUCCESS) 1834 return; 1835 1836 len = sizeof(AutoRun); 1837 lRet = RegQueryValueEx(hKey, 1838 _T("AutoRun"), 1839 NULL, 1840 &dwType, 1841 (LPBYTE)&AutoRun, 1842 &len); 1843 if ((lRet == ERROR_SUCCESS) && (dwType == REG_EXPAND_SZ || dwType == REG_SZ)) 1844 { 1845 if (*AutoRun) 1846 ParseCommandLine(AutoRun); 1847 } 1848 1849 RegCloseKey(hKey); 1850 } 1851 1852 /* Get the command that comes after a /C or /K switch */ 1853 static VOID 1854 GetCmdLineCommand( 1855 OUT LPTSTR commandline, 1856 IN LPCTSTR ptr, 1857 IN BOOL AlwaysStrip) 1858 { 1859 TCHAR* LastQuote; 1860 1861 while (_istspace(*ptr)) 1862 ++ptr; 1863 1864 /* Remove leading quote, find final quote */ 1865 if (*ptr == _T('"') && 1866 (LastQuote = _tcsrchr(++ptr, _T('"'))) != NULL) 1867 { 1868 const TCHAR* Space; 1869 /* Under certain circumstances, all quotes are preserved. 1870 * CMD /? documents these conditions as follows: 1871 * 1. No /S switch 1872 * 2. Exactly two quotes 1873 * 3. No "special characters" between the quotes 1874 * (CMD /? says &<>()@^| but parentheses did not 1875 * trigger this rule when I tested them.) 1876 * 4. Whitespace exists between the quotes 1877 * 5. Enclosed string is an executable filename 1878 */ 1879 *LastQuote = _T('\0'); 1880 for (Space = ptr + 1; Space < LastQuote; ++Space) 1881 { 1882 if (_istspace(*Space)) /* Rule 4 */ 1883 { 1884 if (!AlwaysStrip && /* Rule 1 */ 1885 !_tcspbrk(ptr, _T("\"&<>@^|")) && /* Rules 2, 3 */ 1886 SearchForExecutable(ptr, commandline)) /* Rule 5 */ 1887 { 1888 /* All conditions met: preserve both the quotes */ 1889 *LastQuote = _T('"'); 1890 _tcscpy(commandline, ptr - 1); 1891 return; 1892 } 1893 break; 1894 } 1895 } 1896 1897 /* The conditions were not met: remove both the 1898 * leading quote and the last quote */ 1899 _tcscpy(commandline, ptr); 1900 _tcscpy(&commandline[LastQuote - ptr], LastQuote + 1); 1901 return; 1902 } 1903 1904 /* No quotes; just copy */ 1905 _tcscpy(commandline, ptr); 1906 } 1907 1908 1909 /* 1910 * Set up global initializations and process parameters. 1911 * Return a pointer to the command line if present. 1912 */ 1913 static LPCTSTR 1914 Initialize(VOID) 1915 { 1916 HMODULE NtDllModule; 1917 HANDLE hIn, hOut; 1918 LPTSTR ptr, cmdLine; 1919 TCHAR option = 0; 1920 BOOL AutoRun = TRUE; 1921 TCHAR ModuleName[MAX_PATH + 1]; 1922 1923 /* Get version information */ 1924 InitOSVersion(); 1925 1926 /* Some people like to run ReactOS cmd.exe on Win98, it helps in the 1927 * build process. So don't link implicitly against ntdll.dll, load it 1928 * dynamically instead */ 1929 NtDllModule = GetModuleHandle(TEXT("ntdll.dll")); 1930 if (NtDllModule != NULL) 1931 { 1932 NtQueryInformationProcessPtr = (NtQueryInformationProcessProc)GetProcAddress(NtDllModule, "NtQueryInformationProcess"); 1933 NtReadVirtualMemoryPtr = (NtReadVirtualMemoryProc)GetProcAddress(NtDllModule, "NtReadVirtualMemory"); 1934 } 1935 1936 /* Load the registry settings */ 1937 LoadRegistrySettings(HKEY_LOCAL_MACHINE); 1938 LoadRegistrySettings(HKEY_CURRENT_USER); 1939 1940 /* Initialize our locale */ 1941 InitLocale(); 1942 1943 /* Initialize prompt support */ 1944 InitPrompt(); 1945 1946 #ifdef FEATURE_DIRECTORY_STACK 1947 /* Initialize directory stack */ 1948 InitDirectoryStack(); 1949 #endif 1950 1951 #ifdef FEATURE_HISTORY 1952 /* Initialize history */ 1953 InitHistory(); 1954 #endif 1955 1956 /* Set COMSPEC environment variable */ 1957 if (GetModuleFileName(NULL, ModuleName, ARRAYSIZE(ModuleName)) != 0) 1958 { 1959 ModuleName[MAX_PATH] = _T('\0'); 1960 SetEnvironmentVariable (_T("COMSPEC"), ModuleName); 1961 } 1962 1963 /* Add ctrl break handler */ 1964 AddBreakHandler(); 1965 1966 /* Set our default console mode */ 1967 hOut = ConStreamGetOSHandle(StdOut); 1968 hIn = ConStreamGetOSHandle(StdIn); 1969 SetConsoleMode(hOut, 0); // Reinitialize the console output mode 1970 SetConsoleMode(hOut, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT); 1971 SetConsoleMode(hIn , ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); 1972 1973 cmdLine = GetCommandLine(); 1974 TRACE ("[command args: %s]\n", debugstr_aw(cmdLine)); 1975 1976 for (ptr = cmdLine; *ptr; ++ptr) 1977 { 1978 if (*ptr == _T('/')) 1979 { 1980 option = _totupper(ptr[1]); 1981 if (option == _T('?')) 1982 { 1983 ConOutResPaging(TRUE, STRING_CMD_HELP8); 1984 nErrorLevel = 1; 1985 bExit = TRUE; 1986 return NULL; 1987 } 1988 else if (option == _T('P')) 1989 { 1990 if (!IsExistingFile(_T("\\autoexec.bat"))) 1991 { 1992 #ifdef INCLUDE_CMD_DATE 1993 cmd_date(_T("")); 1994 #endif 1995 #ifdef INCLUDE_CMD_TIME 1996 cmd_time(_T("")); 1997 #endif 1998 } 1999 else 2000 { 2001 ParseCommandLine(_T("\\autoexec.bat")); 2002 } 2003 bCanExit = FALSE; 2004 } 2005 else if (option == _T('A')) 2006 { 2007 OutputStreamMode = AnsiText; 2008 } 2009 else if (option == _T('C') || option == _T('K') || option == _T('R')) 2010 { 2011 /* Remainder of command line is a command to be run */ 2012 fSingleCommand = ((option == _T('K')) << 1) | 1; 2013 break; 2014 } 2015 else if (option == _T('D')) 2016 { 2017 AutoRun = FALSE; 2018 } 2019 else if (option == _T('Q')) 2020 { 2021 bDisableBatchEcho = TRUE; 2022 } 2023 else if (option == _T('S')) 2024 { 2025 bAlwaysStrip = TRUE; 2026 } 2027 #ifdef INCLUDE_CMD_COLOR 2028 else if (!_tcsnicmp(ptr, _T("/T:"), 3)) 2029 { 2030 /* Process /T (color) argument; overwrite any previous settings */ 2031 wDefColor = (WORD)_tcstoul(&ptr[3], &ptr, 16); 2032 } 2033 #endif 2034 else if (option == _T('U')) 2035 { 2036 OutputStreamMode = UTF16Text; 2037 } 2038 else if (option == _T('V')) 2039 { 2040 // FIXME: Check validity of the parameter given to V ! 2041 bDelayedExpansion = _tcsnicmp(&ptr[2], _T(":OFF"), 4); 2042 } 2043 else if (option == _T('E')) 2044 { 2045 // FIXME: Check validity of the parameter given to E ! 2046 bEnableExtensions = _tcsnicmp(&ptr[2], _T(":OFF"), 4); 2047 } 2048 else if (option == _T('X')) 2049 { 2050 /* '/X' is identical to '/E:ON' */ 2051 bEnableExtensions = TRUE; 2052 } 2053 else if (option == _T('Y')) 2054 { 2055 /* '/Y' is identical to '/E:OFF' */ 2056 bEnableExtensions = FALSE; 2057 } 2058 } 2059 } 2060 2061 #ifdef INCLUDE_CMD_COLOR 2062 if (wDefColor == 0) 2063 { 2064 /* 2065 * If we still do not have the console colour attribute set, 2066 * retrieve the default one. 2067 */ 2068 ConGetDefaultAttributes(&wDefColor); 2069 } 2070 2071 if (wDefColor != 0) 2072 ConSetScreenColor(ConStreamGetOSHandle(StdOut), wDefColor, TRUE); 2073 #endif 2074 2075 /* Reset the output Standard Streams translation modes and codepage caches */ 2076 // ConStreamSetMode(StdIn , OutputStreamMode, InputCodePage ); 2077 ConStreamSetMode(StdOut, OutputStreamMode, OutputCodePage); 2078 ConStreamSetMode(StdErr, OutputStreamMode, OutputCodePage); 2079 2080 if (!*ptr) 2081 { 2082 /* If neither /C or /K was given, display a simple version string */ 2083 ConOutChar(_T('\n')); 2084 ConOutResPrintf(STRING_REACTOS_VERSION, 2085 _T(KERNEL_VERSION_STR), 2086 _T(KERNEL_VERSION_BUILD_STR)); 2087 ConOutPuts(_T("(C) Copyright 1998-") _T(COPYRIGHT_YEAR) _T(" ReactOS Team.\n")); 2088 } 2089 2090 if (AutoRun) 2091 { 2092 ExecuteAutoRunFile(HKEY_LOCAL_MACHINE); 2093 ExecuteAutoRunFile(HKEY_CURRENT_USER); 2094 } 2095 2096 /* Returns the rest of the command line */ 2097 return ptr; 2098 } 2099 2100 2101 static VOID Cleanup(VOID) 2102 { 2103 /* Run cmdexit.bat */ 2104 if (IsExistingFile(_T("cmdexit.bat"))) 2105 { 2106 ConErrResPuts(STRING_CMD_ERROR5); 2107 ParseCommandLine(_T("cmdexit.bat")); 2108 } 2109 else if (IsExistingFile(_T("\\cmdexit.bat"))) 2110 { 2111 ConErrResPuts(STRING_CMD_ERROR5); 2112 ParseCommandLine(_T("\\cmdexit.bat")); 2113 } 2114 2115 #ifdef FEATURE_DIRECTORY_STACK 2116 /* Destroy directory stack */ 2117 DestroyDirectoryStack(); 2118 #endif 2119 2120 #ifdef FEATURE_HISTORY 2121 CleanHistory(); 2122 #endif 2123 2124 /* Free GetEnvVar's buffer */ 2125 GetEnvVar(NULL); 2126 2127 /* Remove ctrl break handler */ 2128 RemoveBreakHandler(); 2129 2130 /* Restore the default console mode */ 2131 SetConsoleMode(ConStreamGetOSHandle(StdIn), 2132 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); 2133 SetConsoleMode(ConStreamGetOSHandle(StdOut), 2134 ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT); 2135 2136 DeleteCriticalSection(&ChildProcessRunningLock); 2137 } 2138 2139 /* 2140 * main function 2141 */ 2142 int _tmain(int argc, const TCHAR *argv[]) 2143 { 2144 INT nExitCode; 2145 LPCTSTR pCmdLine; 2146 TCHAR startPath[MAX_PATH]; 2147 2148 InitializeCriticalSection(&ChildProcessRunningLock); 2149 lpOriginalEnvironment = DuplicateEnvironment(); 2150 2151 GetCurrentDirectory(ARRAYSIZE(startPath), startPath); 2152 _tchdir(startPath); 2153 2154 SetFileApisToOEM(); 2155 InputCodePage = GetConsoleCP(); 2156 OutputCodePage = GetConsoleOutputCP(); 2157 2158 /* Initialize the Console Standard Streams */ 2159 ConStreamInit(StdIn , GetStdHandle(STD_INPUT_HANDLE) , /*OutputStreamMode*/ AnsiText, InputCodePage); 2160 ConStreamInit(StdOut, GetStdHandle(STD_OUTPUT_HANDLE), OutputStreamMode, OutputCodePage); 2161 ConStreamInit(StdErr, GetStdHandle(STD_ERROR_HANDLE) , OutputStreamMode, OutputCodePage); 2162 2163 CMD_ModuleHandle = GetModuleHandle(NULL); 2164 2165 /* 2166 * Perform general initialization, parse switches on command-line. 2167 * Initialize the exit code with the errorlevel as Initialize() can set it. 2168 */ 2169 pCmdLine = Initialize(); 2170 nExitCode = nErrorLevel; 2171 2172 if (pCmdLine && *pCmdLine) 2173 { 2174 TCHAR commandline[CMDLINE_LENGTH]; 2175 2176 /* Do the /C or /K command */ 2177 GetCmdLineCommand(commandline, &pCmdLine[2], bAlwaysStrip); 2178 nExitCode = ParseCommandLine(commandline); 2179 if (fSingleCommand == 1) 2180 { 2181 // nErrorLevel = nExitCode; 2182 bExit = TRUE; 2183 } 2184 fSingleCommand = 0; 2185 } 2186 if (!bExit) 2187 { 2188 /* Call prompt routine */ 2189 nExitCode = ProcessInput(); 2190 } 2191 2192 /* Do the cleanup */ 2193 Cleanup(); 2194 cmd_free(lpOriginalEnvironment); 2195 2196 cmd_exit(nExitCode); 2197 return nExitCode; 2198 } 2199 2200 /* EOF */ 2201