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 VOID 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; 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 726 INT 727 ExecuteCommand(PARSED_COMMAND *Cmd) 728 { 729 PARSED_COMMAND *Sub; 730 LPTSTR First, Rest; 731 INT Ret = 0; 732 733 if (!PerformRedirection(Cmd->Redirections)) 734 return 1; 735 736 switch (Cmd->Type) 737 { 738 case C_COMMAND: 739 Ret = 1; 740 First = DoDelayedExpansion(Cmd->Command.First); 741 if (First) 742 { 743 Rest = DoDelayedExpansion(Cmd->Command.Rest); 744 if (Rest) 745 { 746 Ret = DoCommand(First, Rest, Cmd); 747 cmd_free(Rest); 748 } 749 cmd_free(First); 750 } 751 break; 752 case C_QUIET: 753 case C_BLOCK: 754 case C_MULTI: 755 for (Sub = Cmd->Subcommands; Sub; Sub = Sub->Next) 756 Ret = ExecuteCommand(Sub); 757 break; 758 case C_IFFAILURE: 759 Sub = Cmd->Subcommands; 760 Ret = ExecuteCommand(Sub); 761 if (Ret != 0) 762 { 763 nErrorLevel = Ret; 764 Ret = ExecuteCommand(Sub->Next); 765 } 766 break; 767 case C_IFSUCCESS: 768 Sub = Cmd->Subcommands; 769 Ret = ExecuteCommand(Sub); 770 if (Ret == 0) 771 Ret = ExecuteCommand(Sub->Next); 772 break; 773 case C_PIPE: 774 ExecutePipeline(Cmd); 775 break; 776 case C_IF: 777 Ret = ExecuteIf(Cmd); 778 break; 779 case C_FOR: 780 Ret = ExecuteFor(Cmd); 781 break; 782 } 783 784 UndoRedirection(Cmd->Redirections, NULL); 785 return Ret; 786 } 787 788 LPTSTR 789 GetEnvVar(LPCTSTR varName) 790 { 791 static LPTSTR ret = NULL; 792 UINT size; 793 794 cmd_free(ret); 795 ret = NULL; 796 size = GetEnvironmentVariable(varName, NULL, 0); 797 if (size > 0) 798 { 799 ret = cmd_alloc(size * sizeof(TCHAR)); 800 if (ret != NULL) 801 GetEnvironmentVariable(varName, ret, size + 1); 802 } 803 return ret; 804 } 805 806 LPCTSTR 807 GetEnvVarOrSpecial(LPCTSTR varName) 808 { 809 static TCHAR ret[MAX_PATH]; 810 811 LPTSTR var = GetEnvVar(varName); 812 if (var) 813 return var; 814 815 /* env var doesn't exist, look for a "special" one */ 816 /* %CD% */ 817 if (_tcsicmp(varName,_T("cd")) ==0) 818 { 819 GetCurrentDirectory(MAX_PATH, ret); 820 return ret; 821 } 822 /* %TIME% */ 823 else if (_tcsicmp(varName,_T("time")) ==0) 824 { 825 return GetTimeString(); 826 } 827 /* %DATE% */ 828 else if (_tcsicmp(varName,_T("date")) ==0) 829 { 830 return GetDateString(); 831 } 832 833 /* %RANDOM% */ 834 else if (_tcsicmp(varName,_T("random")) ==0) 835 { 836 /* Get random number */ 837 _itot(rand(),ret,10); 838 return ret; 839 } 840 841 /* %CMDCMDLINE% */ 842 else if (_tcsicmp(varName,_T("cmdcmdline")) ==0) 843 { 844 return GetCommandLine(); 845 } 846 847 /* %CMDEXTVERSION% */ 848 else if (_tcsicmp(varName,_T("cmdextversion")) ==0) 849 { 850 /* Set version number to 2 */ 851 _itot(2,ret,10); 852 return ret; 853 } 854 855 /* %ERRORLEVEL% */ 856 else if (_tcsicmp(varName,_T("errorlevel")) ==0) 857 { 858 _itot(nErrorLevel,ret,10); 859 return ret; 860 } 861 862 return NULL; 863 } 864 865 /* Handle the %~var syntax */ 866 static LPTSTR 867 GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, BOOL *)) 868 { 869 static const TCHAR ModifierTable[] = _T("dpnxfsatz"); 870 enum { 871 M_DRIVE = 1, /* D: drive letter */ 872 M_PATH = 2, /* P: path */ 873 M_NAME = 4, /* N: filename */ 874 M_EXT = 8, /* X: extension */ 875 M_FULL = 16, /* F: full path (drive+path+name+ext) */ 876 M_SHORT = 32, /* S: full path (drive+path+name+ext), use short names */ 877 M_ATTR = 64, /* A: attributes */ 878 M_TIME = 128, /* T: modification time */ 879 M_SIZE = 256, /* Z: file size */ 880 } Modifiers = 0; 881 882 TCHAR *Format, *FormatEnd; 883 TCHAR *PathVarName = NULL; 884 LPTSTR Variable; 885 TCHAR *VarEnd; 886 BOOL VariableIsParam0; 887 TCHAR FullPath[MAX_PATH]; 888 TCHAR FixedPath[MAX_PATH], *Filename, *Extension; 889 HANDLE hFind; 890 WIN32_FIND_DATA w32fd; 891 TCHAR *In, *Out; 892 893 static TCHAR Result[CMDLINE_LENGTH]; 894 895 /* There is ambiguity between modifier characters and FOR variables; 896 * the rule that cmd uses is to pick the longest possible match. 897 * For example, if there is a %n variable, then out of %~anxnd, 898 * %~anxn will be substituted rather than just %~an. */ 899 900 /* First, go through as many modifier characters as possible */ 901 FormatEnd = Format = *pFormat; 902 while (*FormatEnd && _tcschr(ModifierTable, _totlower(*FormatEnd))) 903 FormatEnd++; 904 905 if (*FormatEnd == _T('$')) 906 { 907 /* $PATH: syntax */ 908 PathVarName = FormatEnd + 1; 909 FormatEnd = _tcschr(PathVarName, _T(':')); 910 if (!FormatEnd) 911 return NULL; 912 913 /* Must be immediately followed by the variable */ 914 Variable = GetVar(*++FormatEnd, &VariableIsParam0); 915 if (!Variable) 916 return NULL; 917 } 918 else 919 { 920 /* Backtrack if necessary to get a variable name match */ 921 while (!(Variable = GetVar(*FormatEnd, &VariableIsParam0))) 922 { 923 if (FormatEnd == Format) 924 return NULL; 925 FormatEnd--; 926 } 927 } 928 929 for (; Format < FormatEnd && *Format != _T('$'); Format++) 930 Modifiers |= 1 << (_tcschr(ModifierTable, _totlower(*Format)) - ModifierTable); 931 932 *pFormat = FormatEnd + 1; 933 934 /* Exclude the leading and trailing quotes */ 935 VarEnd = &Variable[_tcslen(Variable)]; 936 if (*Variable == _T('"')) 937 { 938 Variable++; 939 if (VarEnd > Variable && VarEnd[-1] == _T('"')) 940 VarEnd--; 941 } 942 943 if ((char *)VarEnd - (char *)Variable >= sizeof Result) 944 return _T(""); 945 memcpy(Result, Variable, (char *)VarEnd - (char *)Variable); 946 Result[VarEnd - Variable] = _T('\0'); 947 948 if (PathVarName) 949 { 950 /* $PATH: syntax - search the directories listed in the 951 * specified environment variable for the file */ 952 LPTSTR PathVar; 953 FormatEnd[-1] = _T('\0'); 954 PathVar = GetEnvVar(PathVarName); 955 FormatEnd[-1] = _T(':'); 956 if (!PathVar || 957 !SearchPath(PathVar, Result, NULL, MAX_PATH, FullPath, NULL)) 958 { 959 return _T(""); 960 } 961 } 962 else if (Modifiers == 0) 963 { 964 /* For plain %~var with no modifiers, just return the variable without quotes */ 965 return Result; 966 } 967 else if (VariableIsParam0) 968 { 969 /* Special case: If the variable is %0 and modifier characters are present, 970 * use the batch file's path (which includes the .bat/.cmd extension) 971 * rather than the actual %0 variable (which might not). */ 972 _tcscpy(FullPath, bc->BatchFilePath); 973 } 974 else 975 { 976 /* Convert the variable, now without quotes, to a full path */ 977 if (!GetFullPathName(Result, MAX_PATH, FullPath, NULL)) 978 return _T(""); 979 } 980 981 /* Next step is to change the path to fix letter case (e.g. 982 * C:\ReAcToS -> C:\ReactOS) and, if requested with the S modifier, 983 * replace long filenames with short. */ 984 985 In = FullPath; 986 Out = FixedPath; 987 988 /* Copy drive letter */ 989 *Out++ = *In++; 990 *Out++ = *In++; 991 *Out++ = *In++; 992 /* Loop over each \-separated component in the path */ 993 do { 994 TCHAR *Next = _tcschr(In, _T('\\')); 995 if (Next) 996 *Next++ = _T('\0'); 997 /* Use FindFirstFile to get the correct name */ 998 if (Out + _tcslen(In) + 1 >= &FixedPath[MAX_PATH]) 999 return _T(""); 1000 _tcscpy(Out, In); 1001 hFind = FindFirstFile(FixedPath, &w32fd); 1002 /* If it doesn't exist, just leave the name as it was given */ 1003 if (hFind != INVALID_HANDLE_VALUE) 1004 { 1005 LPTSTR FixedComponent = w32fd.cFileName; 1006 if (*w32fd.cAlternateFileName && 1007 ((Modifiers & M_SHORT) || !_tcsicmp(In, w32fd.cAlternateFileName))) 1008 { 1009 FixedComponent = w32fd.cAlternateFileName; 1010 } 1011 FindClose(hFind); 1012 1013 if (Out + _tcslen(FixedComponent) + 1 >= &FixedPath[MAX_PATH]) 1014 return _T(""); 1015 _tcscpy(Out, FixedComponent); 1016 } 1017 Filename = Out; 1018 Out += _tcslen(Out); 1019 *Out++ = _T('\\'); 1020 1021 In = Next; 1022 } while (In != NULL); 1023 Out[-1] = _T('\0'); 1024 1025 /* Build the result string. Start with attributes, modification time, and 1026 * file size. If the file didn't exist, these fields will all be empty. */ 1027 Out = Result; 1028 if (hFind != INVALID_HANDLE_VALUE) 1029 { 1030 if (Modifiers & M_ATTR) 1031 { 1032 static const struct { 1033 TCHAR Character; 1034 WORD Value; 1035 } *Attrib, Table[] = { 1036 { _T('d'), FILE_ATTRIBUTE_DIRECTORY }, 1037 { _T('r'), FILE_ATTRIBUTE_READONLY }, 1038 { _T('a'), FILE_ATTRIBUTE_ARCHIVE }, 1039 { _T('h'), FILE_ATTRIBUTE_HIDDEN }, 1040 { _T('s'), FILE_ATTRIBUTE_SYSTEM }, 1041 { _T('c'), FILE_ATTRIBUTE_COMPRESSED }, 1042 { _T('o'), FILE_ATTRIBUTE_OFFLINE }, 1043 { _T('t'), FILE_ATTRIBUTE_TEMPORARY }, 1044 { _T('l'), FILE_ATTRIBUTE_REPARSE_POINT }, 1045 }; 1046 for (Attrib = Table; Attrib != &Table[9]; Attrib++) 1047 { 1048 *Out++ = w32fd.dwFileAttributes & Attrib->Value 1049 ? Attrib->Character 1050 : _T('-'); 1051 } 1052 *Out++ = _T(' '); 1053 } 1054 if (Modifiers & M_TIME) 1055 { 1056 FILETIME ft; 1057 SYSTEMTIME st; 1058 FileTimeToLocalFileTime(&w32fd.ftLastWriteTime, &ft); 1059 FileTimeToSystemTime(&ft, &st); 1060 1061 Out += FormatDate(Out, &st, TRUE); 1062 *Out++ = _T(' '); 1063 Out += FormatTime(Out, &st); 1064 *Out++ = _T(' '); 1065 } 1066 if (Modifiers & M_SIZE) 1067 { 1068 ULARGE_INTEGER Size; 1069 Size.LowPart = w32fd.nFileSizeLow; 1070 Size.HighPart = w32fd.nFileSizeHigh; 1071 Out += _stprintf(Out, _T("%I64u "), Size.QuadPart); 1072 } 1073 } 1074 1075 /* When using the path-searching syntax or the S modifier, 1076 * at least part of the file path is always included. 1077 * If none of the DPNX modifiers are present, include the full path */ 1078 if (PathVarName || (Modifiers & M_SHORT)) 1079 if ((Modifiers & (M_DRIVE | M_PATH | M_NAME | M_EXT)) == 0) 1080 Modifiers |= M_FULL; 1081 1082 /* Now add the requested parts of the name. 1083 * With the F modifier, add all parts to form the full path. */ 1084 Extension = _tcsrchr(Filename, _T('.')); 1085 if (Modifiers & (M_DRIVE | M_FULL)) 1086 { 1087 *Out++ = FixedPath[0]; 1088 *Out++ = FixedPath[1]; 1089 } 1090 if (Modifiers & (M_PATH | M_FULL)) 1091 { 1092 memcpy(Out, &FixedPath[2], (char *)Filename - (char *)&FixedPath[2]); 1093 Out += Filename - &FixedPath[2]; 1094 } 1095 if (Modifiers & (M_NAME | M_FULL)) 1096 { 1097 while (*Filename && Filename != Extension) 1098 *Out++ = *Filename++; 1099 } 1100 if (Modifiers & (M_EXT | M_FULL)) 1101 { 1102 if (Extension) 1103 Out = _stpcpy(Out, Extension); 1104 } 1105 1106 /* Trim trailing space which otherwise would appear as a 1107 * result of using the A/T/Z modifiers but no others. */ 1108 while (Out != &Result[0] && Out[-1] == _T(' ')) 1109 Out--; 1110 *Out = _T('\0'); 1111 1112 return Result; 1113 } 1114 1115 LPCTSTR 1116 GetBatchVar(TCHAR *varName, UINT *varNameLen) 1117 { 1118 LPCTSTR ret; 1119 TCHAR *varNameEnd; 1120 BOOL dummy; 1121 1122 *varNameLen = 1; 1123 1124 switch ( *varName ) 1125 { 1126 case _T('~'): 1127 varNameEnd = varName + 1; 1128 ret = GetEnhancedVar(&varNameEnd, FindArg); 1129 if (!ret) 1130 { 1131 error_syntax(varName); 1132 return NULL; 1133 } 1134 *varNameLen = varNameEnd - varName; 1135 return ret; 1136 case _T('0'): 1137 case _T('1'): 1138 case _T('2'): 1139 case _T('3'): 1140 case _T('4'): 1141 case _T('5'): 1142 case _T('6'): 1143 case _T('7'): 1144 case _T('8'): 1145 case _T('9'): 1146 return FindArg(*varName, &dummy); 1147 1148 case _T('*'): 1149 // 1150 // Copy over the raw params(not including the batch file name 1151 // 1152 return bc->raw_params; 1153 1154 case _T('%'): 1155 return _T("%"); 1156 } 1157 return NULL; 1158 } 1159 1160 BOOL 1161 SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim) 1162 { 1163 #define APPEND(From, Length) { \ 1164 if (Dest + (Length) > DestEnd) \ 1165 goto too_long; \ 1166 memcpy(Dest, From, (Length) * sizeof(TCHAR)); \ 1167 Dest += Length; } 1168 #define APPEND1(Char) { \ 1169 if (Dest >= DestEnd) \ 1170 goto too_long; \ 1171 *Dest++ = Char; } 1172 1173 TCHAR *DestEnd = Dest + CMDLINE_LENGTH - 1; 1174 const TCHAR *Var; 1175 int VarLength; 1176 TCHAR *SubstStart; 1177 TCHAR EndChr; 1178 while (*Src) 1179 { 1180 if (*Src != Delim) 1181 { 1182 APPEND1(*Src++) 1183 continue; 1184 } 1185 1186 Src++; 1187 if (bc && Delim == _T('%')) 1188 { 1189 UINT NameLen; 1190 Var = GetBatchVar(Src, &NameLen); 1191 if (Var != NULL) 1192 { 1193 VarLength = _tcslen(Var); 1194 APPEND(Var, VarLength) 1195 Src += NameLen; 1196 continue; 1197 } 1198 } 1199 1200 /* Find the end of the variable name. A colon (:) will usually 1201 * end the name and begin the optional modifier, but not if it 1202 * is immediately followed by the delimiter (%VAR:%). */ 1203 SubstStart = Src; 1204 while (*Src != Delim && !(*Src == _T(':') && Src[1] != Delim)) 1205 { 1206 if (!*Src) 1207 goto bad_subst; 1208 Src++; 1209 } 1210 1211 EndChr = *Src; 1212 *Src = _T('\0'); 1213 Var = GetEnvVarOrSpecial(SubstStart); 1214 *Src++ = EndChr; 1215 if (Var == NULL) 1216 { 1217 /* In a batch file, %NONEXISTENT% "expands" to an empty string */ 1218 if (bc) 1219 continue; 1220 goto bad_subst; 1221 } 1222 VarLength = _tcslen(Var); 1223 1224 if (EndChr == Delim) 1225 { 1226 /* %VAR% - use as-is */ 1227 APPEND(Var, VarLength) 1228 } 1229 else if (*Src == _T('~')) 1230 { 1231 /* %VAR:~[start][,length]% - substring 1232 * Negative values are offsets from the end */ 1233 int Start = _tcstol(Src + 1, &Src, 0); 1234 int End = VarLength; 1235 if (Start < 0) 1236 Start += VarLength; 1237 Start = max(Start, 0); 1238 Start = min(Start, VarLength); 1239 if (*Src == _T(',')) 1240 { 1241 End = _tcstol(Src + 1, &Src, 0); 1242 End += (End < 0) ? VarLength : Start; 1243 End = max(End, Start); 1244 End = min(End, VarLength); 1245 } 1246 if (*Src++ != Delim) 1247 goto bad_subst; 1248 APPEND(&Var[Start], End - Start); 1249 } 1250 else 1251 { 1252 /* %VAR:old=new% - replace all occurrences of old with new 1253 * %VAR:*old=new% - replace first occurrence only, 1254 * and remove everything before it */ 1255 TCHAR *Old, *New; 1256 DWORD OldLength, NewLength; 1257 BOOL Star = FALSE; 1258 int LastMatch = 0, i = 0; 1259 1260 if (*Src == _T('*')) 1261 { 1262 Star = TRUE; 1263 Src++; 1264 } 1265 1266 /* the string to replace may contain the delimiter */ 1267 Src = _tcschr(Old = Src, _T('=')); 1268 if (Src == NULL) 1269 goto bad_subst; 1270 OldLength = Src++ - Old; 1271 if (OldLength == 0) 1272 goto bad_subst; 1273 1274 Src = _tcschr(New = Src, Delim); 1275 if (Src == NULL) 1276 goto bad_subst; 1277 NewLength = Src++ - New; 1278 1279 while (i < VarLength) 1280 { 1281 if (_tcsnicmp(&Var[i], Old, OldLength) == 0) 1282 { 1283 if (!Star) 1284 APPEND(&Var[LastMatch], i - LastMatch) 1285 APPEND(New, NewLength) 1286 i += OldLength; 1287 LastMatch = i; 1288 if (Star) 1289 break; 1290 continue; 1291 } 1292 i++; 1293 } 1294 APPEND(&Var[LastMatch], VarLength - LastMatch) 1295 } 1296 continue; 1297 1298 bad_subst: 1299 Src = SubstStart; 1300 if (!bc) 1301 APPEND1(Delim) 1302 } 1303 *Dest = _T('\0'); 1304 return TRUE; 1305 too_long: 1306 ConOutResPrintf(STRING_ALIAS_ERROR); 1307 nErrorLevel = 9023; 1308 return FALSE; 1309 #undef APPEND 1310 #undef APPEND1 1311 } 1312 1313 /* Search the list of FOR contexts for a variable */ 1314 static LPTSTR FindForVar(TCHAR Var, BOOL *IsParam0) 1315 { 1316 FOR_CONTEXT *Ctx; 1317 *IsParam0 = FALSE; 1318 for (Ctx = fc; Ctx != NULL; Ctx = Ctx->prev) 1319 { 1320 if ((UINT)(Var - Ctx->firstvar) < Ctx->varcount) 1321 return Ctx->values[Var - Ctx->firstvar]; 1322 } 1323 return NULL; 1324 } 1325 1326 BOOL 1327 SubstituteForVars(TCHAR *Src, TCHAR *Dest) 1328 { 1329 TCHAR *DestEnd = &Dest[CMDLINE_LENGTH - 1]; 1330 while (*Src) 1331 { 1332 if (Src[0] == _T('%')) 1333 { 1334 BOOL Dummy; 1335 LPTSTR End = &Src[2]; 1336 LPTSTR Value = NULL; 1337 1338 if (Src[1] == _T('~')) 1339 Value = GetEnhancedVar(&End, FindForVar); 1340 1341 if (!Value) 1342 Value = FindForVar(Src[1], &Dummy); 1343 1344 if (Value) 1345 { 1346 if (Dest + _tcslen(Value) > DestEnd) 1347 return FALSE; 1348 Dest = _stpcpy(Dest, Value); 1349 Src = End; 1350 continue; 1351 } 1352 } 1353 /* Not a variable; just copy the character */ 1354 if (Dest >= DestEnd) 1355 return FALSE; 1356 *Dest++ = *Src++; 1357 } 1358 *Dest = _T('\0'); 1359 return TRUE; 1360 } 1361 1362 LPTSTR 1363 DoDelayedExpansion(LPTSTR Line) 1364 { 1365 TCHAR Buf1[CMDLINE_LENGTH]; 1366 TCHAR Buf2[CMDLINE_LENGTH]; 1367 1368 /* First, substitute FOR variables */ 1369 if (!SubstituteForVars(Line, Buf1)) 1370 return NULL; 1371 1372 if (!bDelayedExpansion || !_tcschr(Buf1, _T('!'))) 1373 return cmd_dup(Buf1); 1374 1375 /* FIXME: Delayed substitutions actually aren't quite the same as 1376 * immediate substitutions. In particular, it's possible to escape 1377 * the exclamation point using ^. */ 1378 if (!SubstituteVars(Buf1, Buf2, _T('!'))) 1379 return NULL; 1380 return cmd_dup(Buf2); 1381 } 1382 1383 1384 /* 1385 * do the prompt/input/process loop 1386 * 1387 */ 1388 1389 BOOL 1390 ReadLine(TCHAR *commandline, BOOL bMore) 1391 { 1392 TCHAR readline[CMDLINE_LENGTH]; 1393 LPTSTR ip; 1394 1395 /* if no batch input then... */ 1396 if (bc == NULL) 1397 { 1398 if (bMore) 1399 { 1400 ConOutResPrintf(STRING_MORE); 1401 } 1402 else 1403 { 1404 /* JPP 19980807 - if echo off, don't print prompt */ 1405 if (bEcho) 1406 { 1407 if (!bIgnoreEcho) 1408 ConOutChar(_T('\n')); 1409 PrintPrompt(); 1410 } 1411 } 1412 1413 if (!ReadCommand(readline, CMDLINE_LENGTH - 1)) 1414 { 1415 bExit = TRUE; 1416 return FALSE; 1417 } 1418 1419 if (CheckCtrlBreak(BREAK_INPUT)) 1420 { 1421 ConOutChar(_T('\n')); 1422 return FALSE; 1423 } 1424 ip = readline; 1425 } 1426 else 1427 { 1428 ip = ReadBatchLine(); 1429 if (!ip) 1430 return FALSE; 1431 } 1432 1433 return SubstituteVars(ip, commandline, _T('%')); 1434 } 1435 1436 static VOID 1437 ProcessInput(VOID) 1438 { 1439 PARSED_COMMAND *Cmd; 1440 1441 while (!bCanExit || !bExit) 1442 { 1443 Cmd = ParseCommand(NULL); 1444 if (!Cmd) 1445 continue; 1446 1447 ExecuteCommand(Cmd); 1448 FreeCommand(Cmd); 1449 } 1450 } 1451 1452 1453 /* 1454 * control-break handler. 1455 */ 1456 BOOL WINAPI BreakHandler(DWORD dwCtrlType) 1457 { 1458 DWORD dwWritten; 1459 INPUT_RECORD rec; 1460 static BOOL SelfGenerated = FALSE; 1461 1462 if ((dwCtrlType != CTRL_C_EVENT) && 1463 (dwCtrlType != CTRL_BREAK_EVENT)) 1464 { 1465 return FALSE; 1466 } 1467 else 1468 { 1469 if (SelfGenerated) 1470 { 1471 SelfGenerated = FALSE; 1472 return TRUE; 1473 } 1474 } 1475 1476 if (!TryEnterCriticalSection(&ChildProcessRunningLock)) 1477 { 1478 SelfGenerated = TRUE; 1479 GenerateConsoleCtrlEvent (dwCtrlType, 0); 1480 return TRUE; 1481 } 1482 else 1483 { 1484 LeaveCriticalSection(&ChildProcessRunningLock); 1485 } 1486 1487 rec.EventType = KEY_EVENT; 1488 rec.Event.KeyEvent.bKeyDown = TRUE; 1489 rec.Event.KeyEvent.wRepeatCount = 1; 1490 rec.Event.KeyEvent.wVirtualKeyCode = _T('C'); 1491 rec.Event.KeyEvent.wVirtualScanCode = _T('C') - 35; 1492 rec.Event.KeyEvent.uChar.AsciiChar = _T('C'); 1493 rec.Event.KeyEvent.uChar.UnicodeChar = _T('C'); 1494 rec.Event.KeyEvent.dwControlKeyState = RIGHT_CTRL_PRESSED; 1495 1496 WriteConsoleInput(ConStreamGetOSHandle(StdIn), 1497 &rec, 1498 1, 1499 &dwWritten); 1500 1501 bCtrlBreak = TRUE; 1502 /* FIXME: Handle batch files */ 1503 1504 //ConOutPrintf(_T("^C")); 1505 1506 return TRUE; 1507 } 1508 1509 1510 VOID AddBreakHandler(VOID) 1511 { 1512 SetConsoleCtrlHandler(BreakHandler, TRUE); 1513 } 1514 1515 1516 VOID RemoveBreakHandler(VOID) 1517 { 1518 SetConsoleCtrlHandler(BreakHandler, FALSE); 1519 } 1520 1521 1522 /* 1523 * show commands and options that are available. 1524 * 1525 */ 1526 #if 0 1527 static VOID 1528 ShowCommands(VOID) 1529 { 1530 /* print command list */ 1531 ConOutResPuts(STRING_CMD_HELP1); 1532 PrintCommandList(); 1533 1534 /* print feature list */ 1535 ConOutResPuts(STRING_CMD_HELP2); 1536 1537 #ifdef FEATURE_ALIASES 1538 ConOutResPuts(STRING_CMD_HELP3); 1539 #endif 1540 #ifdef FEATURE_HISTORY 1541 ConOutResPuts(STRING_CMD_HELP4); 1542 #endif 1543 #ifdef FEATURE_UNIX_FILENAME_COMPLETION 1544 ConOutResPuts(STRING_CMD_HELP5); 1545 #endif 1546 #ifdef FEATURE_DIRECTORY_STACK 1547 ConOutResPuts(STRING_CMD_HELP6); 1548 #endif 1549 #ifdef FEATURE_REDIRECTION 1550 ConOutResPuts(STRING_CMD_HELP7); 1551 #endif 1552 ConOutChar(_T('\n')); 1553 } 1554 #endif 1555 1556 1557 static VOID 1558 LoadRegistrySettings(HKEY hKeyRoot) 1559 { 1560 LONG lRet; 1561 HKEY hKey; 1562 DWORD dwType, len; 1563 /* 1564 * Buffer big enough to hold the string L"4294967295", 1565 * corresponding to the literal 0xFFFFFFFF (MAX_ULONG) in decimal. 1566 */ 1567 DWORD Buffer[6]; 1568 1569 lRet = RegOpenKeyEx(hKeyRoot, 1570 _T("Software\\Microsoft\\Command Processor"), 1571 0, 1572 KEY_QUERY_VALUE, 1573 &hKey); 1574 if (lRet != ERROR_SUCCESS) 1575 return; 1576 1577 #ifdef INCLUDE_CMD_COLOR 1578 len = sizeof(Buffer); 1579 lRet = RegQueryValueEx(hKey, 1580 _T("DefaultColor"), 1581 NULL, 1582 &dwType, 1583 (LPBYTE)&Buffer, 1584 &len); 1585 if (lRet == ERROR_SUCCESS) 1586 { 1587 /* Overwrite the default attributes */ 1588 if (dwType == REG_DWORD) 1589 wDefColor = (WORD)*(PDWORD)Buffer; 1590 else if (dwType == REG_SZ) 1591 wDefColor = (WORD)_tcstol((PTSTR)Buffer, NULL, 0); 1592 } 1593 // else, use the default attributes retrieved before. 1594 #endif 1595 1596 #if 0 1597 len = sizeof(Buffer); 1598 lRet = RegQueryValueEx(hKey, 1599 _T("DisableUNCCheck"), 1600 NULL, 1601 &dwType, 1602 (LPBYTE)&Buffer, 1603 &len); 1604 if (lRet == ERROR_SUCCESS) 1605 { 1606 /* Overwrite the default setting */ 1607 if (dwType == REG_DWORD) 1608 bDisableUNCCheck = !!*(PDWORD)Buffer; 1609 else if (dwType == REG_SZ) 1610 bDisableUNCCheck = (_ttol((PTSTR)Buffer) == 1); 1611 } 1612 // else, use the default setting set globally. 1613 #endif 1614 1615 len = sizeof(Buffer); 1616 lRet = RegQueryValueEx(hKey, 1617 _T("DelayedExpansion"), 1618 NULL, 1619 &dwType, 1620 (LPBYTE)&Buffer, 1621 &len); 1622 if (lRet == ERROR_SUCCESS) 1623 { 1624 /* Overwrite the default setting */ 1625 if (dwType == REG_DWORD) 1626 bDelayedExpansion = !!*(PDWORD)Buffer; 1627 else if (dwType == REG_SZ) 1628 bDelayedExpansion = (_ttol((PTSTR)Buffer) == 1); 1629 } 1630 // else, use the default setting set globally. 1631 1632 len = sizeof(Buffer); 1633 lRet = RegQueryValueEx(hKey, 1634 _T("EnableExtensions"), 1635 NULL, 1636 &dwType, 1637 (LPBYTE)&Buffer, 1638 &len); 1639 if (lRet == ERROR_SUCCESS) 1640 { 1641 /* Overwrite the default setting */ 1642 if (dwType == REG_DWORD) 1643 bEnableExtensions = !!*(PDWORD)Buffer; 1644 else if (dwType == REG_SZ) 1645 bEnableExtensions = (_ttol((PTSTR)Buffer) == 1); 1646 } 1647 // else, use the default setting set globally. 1648 1649 len = sizeof(Buffer); 1650 lRet = RegQueryValueEx(hKey, 1651 _T("CompletionChar"), 1652 NULL, 1653 &dwType, 1654 (LPBYTE)&Buffer, 1655 &len); 1656 if (lRet == ERROR_SUCCESS) 1657 { 1658 /* Overwrite the default setting */ 1659 if (dwType == REG_DWORD) 1660 AutoCompletionChar = (TCHAR)*(PDWORD)Buffer; 1661 else if (dwType == REG_SZ) 1662 AutoCompletionChar = (TCHAR)_tcstol((PTSTR)Buffer, NULL, 0); 1663 } 1664 // else, use the default setting set globally. 1665 1666 /* Validity check */ 1667 if (IS_COMPLETION_DISABLED(AutoCompletionChar)) 1668 { 1669 /* Disable autocompletion */ 1670 AutoCompletionChar = 0x20; 1671 } 1672 1673 len = sizeof(Buffer); 1674 lRet = RegQueryValueEx(hKey, 1675 _T("PathCompletionChar"), 1676 NULL, 1677 &dwType, 1678 (LPBYTE)&Buffer, 1679 &len); 1680 if (lRet == ERROR_SUCCESS) 1681 { 1682 /* Overwrite the default setting */ 1683 if (dwType == REG_DWORD) 1684 PathCompletionChar = (TCHAR)*(PDWORD)Buffer; 1685 else if (dwType == REG_SZ) 1686 PathCompletionChar = (TCHAR)_tcstol((PTSTR)Buffer, NULL, 0); 1687 } 1688 // else, use the default setting set globally. 1689 1690 /* Validity check */ 1691 if (IS_COMPLETION_DISABLED(PathCompletionChar)) 1692 { 1693 /* Disable autocompletion */ 1694 PathCompletionChar = 0x20; 1695 } 1696 1697 /* Adjust completion chars */ 1698 if (PathCompletionChar >= 0x20 && AutoCompletionChar < 0x20) 1699 PathCompletionChar = AutoCompletionChar; 1700 else if (AutoCompletionChar >= 0x20 && PathCompletionChar < 0x20) 1701 AutoCompletionChar = PathCompletionChar; 1702 1703 RegCloseKey(hKey); 1704 } 1705 1706 static VOID 1707 ExecuteAutoRunFile(HKEY hKeyRoot) 1708 { 1709 LONG lRet; 1710 HKEY hKey; 1711 DWORD dwType, len; 1712 TCHAR AutoRun[2048]; 1713 1714 lRet = RegOpenKeyEx(hKeyRoot, 1715 _T("Software\\Microsoft\\Command Processor"), 1716 0, 1717 KEY_QUERY_VALUE, 1718 &hKey); 1719 if (lRet != ERROR_SUCCESS) 1720 return; 1721 1722 len = sizeof(AutoRun); 1723 lRet = RegQueryValueEx(hKey, 1724 _T("AutoRun"), 1725 NULL, 1726 &dwType, 1727 (LPBYTE)&AutoRun, 1728 &len); 1729 if ((lRet == ERROR_SUCCESS) && (dwType == REG_EXPAND_SZ || dwType == REG_SZ)) 1730 { 1731 if (*AutoRun) 1732 ParseCommandLine(AutoRun); 1733 } 1734 1735 RegCloseKey(hKey); 1736 } 1737 1738 /* Get the command that comes after a /C or /K switch */ 1739 static VOID 1740 GetCmdLineCommand(TCHAR *commandline, TCHAR *ptr, BOOL AlwaysStrip) 1741 { 1742 TCHAR *LastQuote; 1743 1744 while (_istspace(*ptr)) 1745 ptr++; 1746 1747 /* Remove leading quote, find final quote */ 1748 if (*ptr == _T('"') && 1749 (LastQuote = _tcsrchr(++ptr, _T('"'))) != NULL) 1750 { 1751 TCHAR *Space; 1752 /* Under certain circumstances, all quotes are preserved. 1753 * CMD /? documents these conditions as follows: 1754 * 1. No /S switch 1755 * 2. Exactly two quotes 1756 * 3. No "special characters" between the quotes 1757 * (CMD /? says &<>()@^| but parentheses did not 1758 * trigger this rule when I tested them.) 1759 * 4. Whitespace exists between the quotes 1760 * 5. Enclosed string is an executable filename 1761 */ 1762 *LastQuote = _T('\0'); 1763 for (Space = ptr + 1; Space < LastQuote; Space++) 1764 { 1765 if (_istspace(*Space)) /* Rule 4 */ 1766 { 1767 if (!AlwaysStrip && /* Rule 1 */ 1768 !_tcspbrk(ptr, _T("\"&<>@^|")) && /* Rules 2, 3 */ 1769 SearchForExecutable(ptr, commandline)) /* Rule 5 */ 1770 { 1771 /* All conditions met: preserve both the quotes */ 1772 *LastQuote = _T('"'); 1773 _tcscpy(commandline, ptr - 1); 1774 return; 1775 } 1776 break; 1777 } 1778 } 1779 1780 /* The conditions were not met: remove both the 1781 * leading quote and the last quote */ 1782 _tcscpy(commandline, ptr); 1783 _tcscpy(&commandline[LastQuote - ptr], LastQuote + 1); 1784 return; 1785 } 1786 1787 /* No quotes; just copy */ 1788 _tcscpy(commandline, ptr); 1789 } 1790 1791 1792 /* 1793 * Set up global initializations and process parameters 1794 */ 1795 static VOID 1796 Initialize(VOID) 1797 { 1798 HMODULE NtDllModule; 1799 TCHAR commandline[CMDLINE_LENGTH]; 1800 TCHAR ModuleName[_MAX_PATH + 1]; 1801 INT nExitCode; 1802 1803 HANDLE hIn, hOut; 1804 1805 TCHAR *ptr, *cmdLine, option = 0; 1806 BOOL AlwaysStrip = FALSE; 1807 BOOL AutoRun = TRUE; 1808 1809 /* Get version information */ 1810 InitOSVersion(); 1811 1812 /* Some people like to run ReactOS cmd.exe on Win98, it helps in the 1813 * build process. So don't link implicitly against ntdll.dll, load it 1814 * dynamically instead */ 1815 NtDllModule = GetModuleHandle(TEXT("ntdll.dll")); 1816 if (NtDllModule != NULL) 1817 { 1818 NtQueryInformationProcessPtr = (NtQueryInformationProcessProc)GetProcAddress(NtDllModule, "NtQueryInformationProcess"); 1819 NtReadVirtualMemoryPtr = (NtReadVirtualMemoryProc)GetProcAddress(NtDllModule, "NtReadVirtualMemory"); 1820 } 1821 1822 /* Load the registry settings */ 1823 LoadRegistrySettings(HKEY_LOCAL_MACHINE); 1824 LoadRegistrySettings(HKEY_CURRENT_USER); 1825 1826 /* Initialize our locale */ 1827 InitLocale(); 1828 1829 /* Initialize prompt support */ 1830 InitPrompt(); 1831 1832 #ifdef FEATURE_DIR_STACK 1833 /* Initialize directory stack */ 1834 InitDirectoryStack(); 1835 #endif 1836 1837 #ifdef FEATURE_HISTORY 1838 /* Initialize history */ 1839 InitHistory(); 1840 #endif 1841 1842 /* Set COMSPEC environment variable */ 1843 if (GetModuleFileName(NULL, ModuleName, ARRAYSIZE(ModuleName)) != 0) 1844 { 1845 ModuleName[_MAX_PATH] = _T('\0'); 1846 SetEnvironmentVariable (_T("COMSPEC"), ModuleName); 1847 } 1848 1849 /* Add ctrl break handler */ 1850 AddBreakHandler(); 1851 1852 /* Set our default console mode */ 1853 hOut = ConStreamGetOSHandle(StdOut); 1854 hIn = ConStreamGetOSHandle(StdIn); 1855 SetConsoleMode(hOut, 0); // Reinitialize the console output mode 1856 SetConsoleMode(hOut, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT); 1857 SetConsoleMode(hIn , ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); 1858 1859 cmdLine = GetCommandLine(); 1860 TRACE ("[command args: %s]\n", debugstr_aw(cmdLine)); 1861 1862 for (ptr = cmdLine; *ptr; ptr++) 1863 { 1864 if (*ptr == _T('/')) 1865 { 1866 option = _totupper(ptr[1]); 1867 if (option == _T('?')) 1868 { 1869 ConOutResPaging(TRUE,STRING_CMD_HELP8); 1870 nErrorLevel = 1; 1871 bExit = TRUE; 1872 return; 1873 } 1874 else if (option == _T('P')) 1875 { 1876 if (!IsExistingFile (_T("\\autoexec.bat"))) 1877 { 1878 #ifdef INCLUDE_CMD_DATE 1879 cmd_date (_T("")); 1880 #endif 1881 #ifdef INCLUDE_CMD_TIME 1882 cmd_time (_T("")); 1883 #endif 1884 } 1885 else 1886 { 1887 ParseCommandLine (_T("\\autoexec.bat")); 1888 } 1889 bCanExit = FALSE; 1890 } 1891 else if (option == _T('A')) 1892 { 1893 OutputStreamMode = AnsiText; 1894 } 1895 else if (option == _T('C') || option == _T('K') || option == _T('R')) 1896 { 1897 /* Remainder of command line is a command to be run */ 1898 break; 1899 } 1900 else if (option == _T('D')) 1901 { 1902 AutoRun = FALSE; 1903 } 1904 else if (option == _T('Q')) 1905 { 1906 bDisableBatchEcho = TRUE; 1907 } 1908 else if (option == _T('S')) 1909 { 1910 AlwaysStrip = TRUE; 1911 } 1912 #ifdef INCLUDE_CMD_COLOR 1913 else if (!_tcsnicmp(ptr, _T("/T:"), 3)) 1914 { 1915 /* Process /T (color) argument; overwrite any previous settings */ 1916 wDefColor = (WORD)_tcstoul(&ptr[3], &ptr, 16); 1917 } 1918 #endif 1919 else if (option == _T('U')) 1920 { 1921 OutputStreamMode = UTF16Text; 1922 } 1923 else if (option == _T('V')) 1924 { 1925 // FIXME: Check validity of the parameter given to V ! 1926 bDelayedExpansion = _tcsnicmp(&ptr[2], _T(":OFF"), 4); 1927 } 1928 else if (option == _T('E')) 1929 { 1930 // FIXME: Check validity of the parameter given to E ! 1931 bEnableExtensions = _tcsnicmp(&ptr[2], _T(":OFF"), 4); 1932 } 1933 else if (option == _T('X')) 1934 { 1935 /* '/X' is identical to '/E:ON' */ 1936 bEnableExtensions = TRUE; 1937 } 1938 else if (option == _T('Y')) 1939 { 1940 /* '/Y' is identical to '/E:OFF' */ 1941 bEnableExtensions = FALSE; 1942 } 1943 } 1944 } 1945 1946 #ifdef INCLUDE_CMD_COLOR 1947 if (wDefColor == 0) 1948 { 1949 /* 1950 * If we still do not have the console colour attribute set, 1951 * retrieve the default one. 1952 */ 1953 ConGetDefaultAttributes(&wDefColor); 1954 } 1955 1956 if (wDefColor != 0) 1957 ConSetScreenColor(ConStreamGetOSHandle(StdOut), wDefColor, TRUE); 1958 #endif 1959 1960 /* Reset the output Standard Streams translation modes and codepage caches */ 1961 // ConStreamSetMode(StdIn , OutputStreamMode, InputCodePage ); 1962 ConStreamSetMode(StdOut, OutputStreamMode, OutputCodePage); 1963 ConStreamSetMode(StdErr, OutputStreamMode, OutputCodePage); 1964 1965 if (!*ptr) 1966 { 1967 /* If neither /C or /K was given, display a simple version string */ 1968 ConOutChar(_T('\n')); 1969 ConOutResPrintf(STRING_REACTOS_VERSION, 1970 _T(KERNEL_VERSION_STR), 1971 _T(KERNEL_VERSION_BUILD_STR)); 1972 ConOutPuts(_T("(C) Copyright 1998-") _T(COPYRIGHT_YEAR) _T(" ReactOS Team.\n")); 1973 } 1974 1975 if (AutoRun) 1976 { 1977 ExecuteAutoRunFile(HKEY_LOCAL_MACHINE); 1978 ExecuteAutoRunFile(HKEY_CURRENT_USER); 1979 } 1980 1981 if (*ptr) 1982 { 1983 /* Do the /C or /K command */ 1984 GetCmdLineCommand(commandline, &ptr[2], AlwaysStrip); 1985 bWaitForCommand = TRUE; 1986 nExitCode = ParseCommandLine(commandline); 1987 bWaitForCommand = FALSE; 1988 if (option != _T('K')) 1989 { 1990 nErrorLevel = nExitCode; 1991 bExit = TRUE; 1992 } 1993 } 1994 } 1995 1996 1997 static VOID Cleanup(VOID) 1998 { 1999 /* Run cmdexit.bat */ 2000 if (IsExistingFile(_T("cmdexit.bat"))) 2001 { 2002 ConErrResPuts(STRING_CMD_ERROR5); 2003 ParseCommandLine(_T("cmdexit.bat")); 2004 } 2005 else if (IsExistingFile(_T("\\cmdexit.bat"))) 2006 { 2007 ConErrResPuts(STRING_CMD_ERROR5); 2008 ParseCommandLine(_T("\\cmdexit.bat")); 2009 } 2010 2011 #ifdef FEATURE_DIRECTORY_STACK 2012 /* Destroy directory stack */ 2013 DestroyDirectoryStack(); 2014 #endif 2015 2016 #ifdef FEATURE_HISTORY 2017 CleanHistory(); 2018 #endif 2019 2020 /* Free GetEnvVar's buffer */ 2021 GetEnvVar(NULL); 2022 2023 /* Remove ctrl break handler */ 2024 RemoveBreakHandler(); 2025 2026 /* Restore the default console mode */ 2027 SetConsoleMode(ConStreamGetOSHandle(StdIn), 2028 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); 2029 SetConsoleMode(ConStreamGetOSHandle(StdOut), 2030 ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT); 2031 2032 DeleteCriticalSection(&ChildProcessRunningLock); 2033 } 2034 2035 /* 2036 * main function 2037 */ 2038 int _tmain(int argc, const TCHAR *argv[]) 2039 { 2040 TCHAR startPath[MAX_PATH]; 2041 2042 InitializeCriticalSection(&ChildProcessRunningLock); 2043 lpOriginalEnvironment = DuplicateEnvironment(); 2044 2045 GetCurrentDirectory(ARRAYSIZE(startPath), startPath); 2046 _tchdir(startPath); 2047 2048 SetFileApisToOEM(); 2049 InputCodePage = GetConsoleCP(); 2050 OutputCodePage = GetConsoleOutputCP(); 2051 2052 /* Initialize the Console Standard Streams */ 2053 ConStreamInit(StdIn , GetStdHandle(STD_INPUT_HANDLE) , /*OutputStreamMode*/ AnsiText, InputCodePage); 2054 ConStreamInit(StdOut, GetStdHandle(STD_OUTPUT_HANDLE), OutputStreamMode, OutputCodePage); 2055 ConStreamInit(StdErr, GetStdHandle(STD_ERROR_HANDLE) , OutputStreamMode, OutputCodePage); 2056 2057 CMD_ModuleHandle = GetModuleHandle(NULL); 2058 2059 /* Perform general initialization, parse switches on command-line */ 2060 Initialize(); 2061 2062 /* Call prompt routine */ 2063 ProcessInput(); 2064 2065 /* Do the cleanup */ 2066 Cleanup(); 2067 2068 cmd_free(lpOriginalEnvironment); 2069 2070 cmd_exit(nErrorLevel); 2071 return nErrorLevel; 2072 } 2073 2074 /* EOF */ 2075