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