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