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