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 the 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 the local code page cache */ 517 { 518 UINT uNewInputCodePage = GetConsoleCP(); 519 UINT uNewOutputCodePage = GetConsoleOutputCP(); 520 521 if ((InputCodePage != uNewInputCodePage) || 522 (OutputCodePage != uNewOutputCodePage)) 523 { 524 InputCodePage = uNewInputCodePage; 525 OutputCodePage = uNewOutputCodePage; 526 527 /* Reset the current thread UI language */ 528 if (IsConsoleHandle(ConStreamGetOSHandle(StdOut)) || 529 IsConsoleHandle(ConStreamGetOSHandle(StdErr))) 530 { 531 ConSetThreadUILanguage(0); 532 } 533 /* Update the streams cached code page */ 534 ConStdStreamsSetCacheCodePage(InputCodePage, OutputCodePage); 535 536 /* Update the locale as well */ 537 InitLocale(); 538 } 539 } 540 541 /* Restore the original console title */ 542 ResetConTitle(); 543 544 return dwExitCode; 545 } 546 547 548 /* 549 * Look through the internal commands and determine whether or not this 550 * command is one of them. If it is, call the command. If not, call 551 * execute to run it as an external program. 552 * 553 * first - first word on command line 554 * rest - rest of command line 555 */ 556 INT 557 DoCommand(LPTSTR first, LPTSTR rest, PARSED_COMMAND *Cmd) 558 { 559 TCHAR *com; 560 TCHAR *cp; 561 LPTSTR param; /* Pointer to command's parameters */ 562 INT cl; 563 LPCOMMAND cmdptr; 564 BOOL nointernal = FALSE; 565 INT ret; 566 567 TRACE ("DoCommand: (\'%s\' \'%s\')\n", debugstr_aw(first), debugstr_aw(rest)); 568 569 /* Full command line */ 570 com = cmd_alloc((_tcslen(first) + _tcslen(rest) + 2) * sizeof(TCHAR)); 571 if (com == NULL) 572 { 573 error_out_of_memory(); 574 return 1; 575 } 576 577 /* If present in the first word, these characters end the name of an 578 * internal command and become the beginning of its parameters. */ 579 cp = first + _tcscspn(first, _T("\t +,/;=[]")); 580 581 for (cl = 0; cl < (cp - first); cl++) 582 { 583 /* These characters do it too, but if one of them is present, 584 * then we check to see if the word is a file name and skip 585 * checking for internal commands if so. 586 * This allows running programs with names like "echo.exe" */ 587 if (_tcschr(_T(".:\\"), first[cl])) 588 { 589 TCHAR tmp = *cp; 590 *cp = _T('\0'); 591 nointernal = IsExistingFile(first); 592 *cp = tmp; 593 break; 594 } 595 } 596 597 /* Scan internal command table */ 598 for (cmdptr = cmds; !nointernal && cmdptr->name; cmdptr++) 599 { 600 if (!_tcsnicmp(first, cmdptr->name, cl) && cmdptr->name[cl] == _T('\0')) 601 { 602 _tcscpy(com, first); 603 _tcscat(com, rest); 604 param = &com[cl]; 605 606 /* Skip over whitespace to rest of line, exclude 'echo' command */ 607 if (_tcsicmp(cmdptr->name, _T("echo")) != 0) 608 { 609 while (_istspace(*param)) 610 param++; 611 } 612 613 /* Set the new console title */ 614 SetConTitle(com); 615 616 ret = cmdptr->func(param); 617 618 /* Restore the original console title */ 619 ResetConTitle(); 620 621 cmd_free(com); 622 return ret; 623 } 624 } 625 626 ret = Execute(com, first, rest, Cmd); 627 cmd_free(com); 628 return ret; 629 } 630 631 632 /* 633 * process the command line and execute the appropriate functions 634 * full input/output redirection and piping are supported 635 */ 636 INT ParseCommandLine(LPTSTR cmd) 637 { 638 INT Ret = 0; 639 PARSED_COMMAND *Cmd; 640 641 Cmd = ParseCommand(cmd); 642 if (!Cmd) 643 { 644 /* Return an adequate error code */ 645 return (!bParseError ? 0 : 1); 646 } 647 648 Ret = ExecuteCommand(Cmd); 649 FreeCommand(Cmd); 650 return Ret; 651 } 652 653 /* Execute a command without waiting for it to finish. If it's an internal 654 * command or batch file, we must create a new cmd.exe process to handle it. 655 * TODO: For now, this just always creates a cmd.exe process. 656 * This works, but is inefficient for running external programs, 657 * which could just be run directly. */ 658 static HANDLE 659 ExecuteAsync(PARSED_COMMAND *Cmd) 660 { 661 TCHAR CmdPath[MAX_PATH]; 662 TCHAR CmdParams[CMDLINE_LENGTH], *ParamsEnd; 663 STARTUPINFO stui; 664 PROCESS_INFORMATION prci; 665 666 /* Get the path to cmd.exe */ 667 GetModuleFileName(NULL, CmdPath, ARRAYSIZE(CmdPath)); 668 669 /* Build the parameter string to pass to cmd.exe */ 670 ParamsEnd = _stpcpy(CmdParams, _T("/S/D/C\"")); 671 ParamsEnd = UnparseCommand(Cmd, ParamsEnd, &CmdParams[CMDLINE_LENGTH - 2]); 672 if (!ParamsEnd) 673 { 674 error_out_of_memory(); 675 return NULL; 676 } 677 _tcscpy(ParamsEnd, _T("\"")); 678 679 memset(&stui, 0, sizeof stui); 680 stui.cb = sizeof(STARTUPINFO); 681 if (!CreateProcess(CmdPath, CmdParams, NULL, NULL, TRUE, 0, 682 NULL, NULL, &stui, &prci)) 683 { 684 ErrorMessage(GetLastError(), NULL); 685 return NULL; 686 } 687 688 CloseHandle(prci.hThread); 689 return prci.hProcess; 690 } 691 692 static INT 693 ExecutePipeline(PARSED_COMMAND *Cmd) 694 { 695 #ifdef FEATURE_REDIRECTION 696 HANDLE hInput = NULL; 697 HANDLE hOldConIn = GetStdHandle(STD_INPUT_HANDLE); 698 HANDLE hOldConOut = GetStdHandle(STD_OUTPUT_HANDLE); 699 HANDLE hProcess[MAXIMUM_WAIT_OBJECTS]; 700 INT nProcesses = 0; 701 DWORD dwExitCode; 702 703 /* Do all but the last pipe command */ 704 do 705 { 706 HANDLE hPipeRead, hPipeWrite; 707 if (nProcesses > (MAXIMUM_WAIT_OBJECTS - 2)) 708 { 709 error_too_many_parameters(_T("|")); 710 goto failed; 711 } 712 713 /* Create the pipe that this process will write into. 714 * Make the handles non-inheritable initially, because this 715 * process shouldn't inherit the reading handle. */ 716 if (!CreatePipe(&hPipeRead, &hPipeWrite, NULL, 0)) 717 { 718 error_no_pipe(); 719 goto failed; 720 } 721 722 /* The writing side of the pipe is STDOUT for this process */ 723 SetHandleInformation(hPipeWrite, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); 724 SetStdHandle(STD_OUTPUT_HANDLE, hPipeWrite); 725 726 /* Execute it (error check is done later for easier cleanup) */ 727 hProcess[nProcesses] = ExecuteAsync(Cmd->Subcommands); 728 CloseHandle(hPipeWrite); 729 if (hInput) 730 CloseHandle(hInput); 731 732 /* The reading side of the pipe will be STDIN for the next process */ 733 SetHandleInformation(hPipeRead, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); 734 SetStdHandle(STD_INPUT_HANDLE, hPipeRead); 735 hInput = hPipeRead; 736 737 if (!hProcess[nProcesses]) 738 goto failed; 739 nProcesses++; 740 741 Cmd = Cmd->Subcommands->Next; 742 } while (Cmd->Type == C_PIPE); 743 744 /* The last process uses the original STDOUT */ 745 SetStdHandle(STD_OUTPUT_HANDLE, hOldConOut); 746 hProcess[nProcesses] = ExecuteAsync(Cmd); 747 if (!hProcess[nProcesses]) 748 goto failed; 749 nProcesses++; 750 CloseHandle(hInput); 751 SetStdHandle(STD_INPUT_HANDLE, hOldConIn); 752 753 /* Wait for all processes to complete */ 754 EnterCriticalSection(&ChildProcessRunningLock); 755 WaitForMultipleObjects(nProcesses, hProcess, TRUE, INFINITE); 756 LeaveCriticalSection(&ChildProcessRunningLock); 757 758 /* Use the exit code of the last process in the pipeline */ 759 GetExitCodeProcess(hProcess[nProcesses - 1], &dwExitCode); 760 nErrorLevel = (INT)dwExitCode; 761 762 while (--nProcesses >= 0) 763 CloseHandle(hProcess[nProcesses]); 764 return nErrorLevel; 765 766 failed: 767 if (hInput) 768 CloseHandle(hInput); 769 while (--nProcesses >= 0) 770 { 771 TerminateProcess(hProcess[nProcesses], 0); 772 CloseHandle(hProcess[nProcesses]); 773 } 774 SetStdHandle(STD_INPUT_HANDLE, hOldConIn); 775 SetStdHandle(STD_OUTPUT_HANDLE, hOldConOut); 776 #endif 777 778 return nErrorLevel; 779 } 780 781 INT 782 ExecuteCommand( 783 IN PARSED_COMMAND *Cmd) 784 { 785 #define SeenGoto() \ 786 (bc && bc->current == NULL) 787 788 PARSED_COMMAND *Sub; 789 LPTSTR First, Rest; 790 INT Ret = 0; 791 792 /* If we don't have any command, or if this is REM, ignore it */ 793 if (!Cmd || (Cmd->Type == C_REM)) 794 return 0; 795 /* 796 * Do not execute any command if we are about to exit CMD, or about to 797 * change batch execution context, e.g. in case of a CALL / GOTO / EXIT. 798 */ 799 if (bExit || SeenGoto()) 800 return 0; 801 802 if (!PerformRedirection(Cmd->Redirections)) 803 return 1; 804 805 switch (Cmd->Type) 806 { 807 case C_COMMAND: 808 Ret = 1; 809 First = DoDelayedExpansion(Cmd->Command.First); 810 if (First) 811 { 812 Rest = DoDelayedExpansion(Cmd->Command.Rest); 813 if (Rest) 814 { 815 Ret = DoCommand(First, Rest, Cmd); 816 cmd_free(Rest); 817 } 818 cmd_free(First); 819 } 820 /* Fall through */ 821 case C_REM: 822 break; 823 824 case C_QUIET: 825 case C_BLOCK: 826 case C_MULTI: 827 for (Sub = Cmd->Subcommands; Sub && !SeenGoto(); Sub = Sub->Next) 828 Ret = ExecuteCommand(Sub); 829 break; 830 831 case C_OR: 832 Sub = Cmd->Subcommands; 833 Ret = ExecuteCommand(Sub); 834 if ((Ret != 0) && !SeenGoto()) 835 { 836 nErrorLevel = Ret; 837 Ret = ExecuteCommand(Sub->Next); 838 } 839 break; 840 841 case C_AND: 842 Sub = Cmd->Subcommands; 843 Ret = ExecuteCommand(Sub); 844 if ((Ret == 0) && !SeenGoto()) 845 Ret = ExecuteCommand(Sub->Next); 846 break; 847 848 case C_PIPE: 849 Ret = ExecutePipeline(Cmd); 850 break; 851 852 case C_FOR: 853 Ret = ExecuteFor(Cmd); 854 break; 855 856 case C_IF: 857 Ret = ExecuteIf(Cmd); 858 break; 859 } 860 861 UndoRedirection(Cmd->Redirections, NULL); 862 return Ret; 863 864 #undef SeenGoto 865 } 866 867 INT 868 ExecuteCommandWithEcho( 869 IN PARSED_COMMAND *Cmd) 870 { 871 /* Echo the reconstructed command line */ 872 if (bEcho && !bDisableBatchEcho && Cmd && (Cmd->Type != C_QUIET)) 873 { 874 if (!bIgnoreEcho) 875 ConOutChar(_T('\n')); 876 PrintPrompt(); 877 EchoCommand(Cmd); 878 ConOutChar(_T('\n')); 879 } 880 881 /* Run the command */ 882 return ExecuteCommand(Cmd); 883 } 884 885 LPTSTR 886 GetEnvVar(LPCTSTR varName) 887 { 888 static LPTSTR ret = NULL; 889 UINT size; 890 891 cmd_free(ret); 892 ret = NULL; 893 size = GetEnvironmentVariable(varName, NULL, 0); 894 if (size > 0) 895 { 896 ret = cmd_alloc(size * sizeof(TCHAR)); 897 if (ret != NULL) 898 GetEnvironmentVariable(varName, ret, size + 1); 899 } 900 return ret; 901 } 902 903 LPCTSTR 904 GetEnvVarOrSpecial(LPCTSTR varName) 905 { 906 static TCHAR ret[MAX_PATH]; 907 908 LPTSTR var = GetEnvVar(varName); 909 if (var) 910 return var; 911 912 /* The environment variable doesn't exist, look for 913 * a "special" one only if extensions are enabled. */ 914 if (!bEnableExtensions) 915 return NULL; 916 917 /* %CD% */ 918 if (_tcsicmp(varName, _T("CD")) == 0) 919 { 920 GetCurrentDirectory(ARRAYSIZE(ret), ret); 921 return ret; 922 } 923 /* %DATE% */ 924 else if (_tcsicmp(varName, _T("DATE")) == 0) 925 { 926 return GetDateString(); 927 } 928 /* %TIME% */ 929 else if (_tcsicmp(varName, _T("TIME")) == 0) 930 { 931 return GetTimeString(); 932 } 933 /* %RANDOM% */ 934 else if (_tcsicmp(varName, _T("RANDOM")) == 0) 935 { 936 /* Get random number */ 937 _itot(rand(), ret, 10); 938 return ret; 939 } 940 /* %CMDCMDLINE% */ 941 else if (_tcsicmp(varName, _T("CMDCMDLINE")) == 0) 942 { 943 return GetCommandLine(); 944 } 945 /* %CMDEXTVERSION% */ 946 else if (_tcsicmp(varName, _T("CMDEXTVERSION")) == 0) 947 { 948 /* Set Command Extensions version number to CMDEXTVERSION */ 949 _itot(CMDEXTVERSION, ret, 10); 950 return ret; 951 } 952 /* %ERRORLEVEL% */ 953 else if (_tcsicmp(varName, _T("ERRORLEVEL")) == 0) 954 { 955 _itot(nErrorLevel, ret, 10); 956 return ret; 957 } 958 #if (NTDDI_VERSION >= NTDDI_WIN7) 959 /* Available in Win7+, even if the underlying API is available in Win2003+ */ 960 /* %HIGHESTNUMANODENUMBER% */ 961 else if (_tcsicmp(varName, _T("HIGHESTNUMANODENUMBER")) == 0) 962 { 963 ULONG NumaNodeNumber = 0; 964 GetNumaHighestNodeNumber(&NumaNodeNumber); 965 _itot(NumaNodeNumber, ret, 10); 966 return ret; 967 } 968 #endif 969 970 return NULL; 971 } 972 973 /* Handle the %~var syntax */ 974 static PCTSTR 975 GetEnhancedVar( 976 IN OUT PCTSTR* pFormat, 977 IN BOOL (*GetVar)(TCHAR, PCTSTR*, BOOL*)) 978 { 979 static const TCHAR ModifierTable[] = _T("dpnxfsatz"); 980 enum { 981 M_DRIVE = 1, /* D: drive letter */ 982 M_PATH = 2, /* P: path */ 983 M_NAME = 4, /* N: filename */ 984 M_EXT = 8, /* X: extension */ 985 M_FULL = 16, /* F: full path (drive+path+name+ext) */ 986 M_SHORT = 32, /* S: full path (drive+path+name+ext), use short names */ 987 M_ATTR = 64, /* A: attributes */ 988 M_TIME = 128, /* T: modification time */ 989 M_SIZE = 256, /* Z: file size */ 990 } Modifiers = 0; 991 992 PCTSTR Format, FormatEnd; 993 PCTSTR PathVarName = NULL; 994 PCTSTR Variable; 995 PCTSTR VarEnd; 996 BOOL VariableIsParam0; 997 TCHAR FullPath[MAX_PATH]; 998 TCHAR FixedPath[MAX_PATH]; 999 PTCHAR Filename, Extension; 1000 HANDLE hFind; 1001 WIN32_FIND_DATA w32fd; 1002 PTCHAR In, Out; 1003 1004 static TCHAR Result[CMDLINE_LENGTH]; 1005 1006 /* Check whether the current character is a recognized variable. 1007 * If it is not, then restore the previous one: there is indeed 1008 * ambiguity between modifier characters and FOR variables; 1009 * the rule that CMD uses is to pick the longest possible match. 1010 * This case can happen if we have a FOR-variable specification 1011 * of the following form: 1012 * 1013 * %~<modifiers><actual FOR variable character><other characters> 1014 * 1015 * where the FOR variable character is also a similar to a modifier, 1016 * but should not be interpreted as is, and the following other 1017 * characters are not part of the possible modifier characters, and 1018 * are unrelated to the FOR variable (they can be part of a command). 1019 * For example, if there is a %n variable, then out of %~anxnd, 1020 * %~anxn will be substituted rather than just %~an. 1021 * 1022 * In the following examples, all characters 'd','p','n','x' are valid modifiers. 1023 * 1024 * 1. In this example, the FOR variable character is 'x' and the actual 1025 * modifiers are 'dpn'. Parsing will first determine that 'dpnx' 1026 * are modifiers, with the possible (last) valid variable being 'x', 1027 * and will stop at the letter 'g'. Since 'g' is not a valid 1028 * variable, then the actual variable is the lattest one 'x', 1029 * and the modifiers are then actually 'dpn'. 1030 * The FOR-loop will then display the %x variable formatted with 'dpn' 1031 * and will append any other characters following, 'g'. 1032 * 1033 * C:\Temp>for %x in (foo.exe bar.txt) do @echo %~dpnxg 1034 * C:\Temp\foog 1035 * C:\Temp\barg 1036 * 1037 * 1038 * 2. In this second example, the FOR variable character is 'g' and 1039 * the actual modifiers are 'dpnx'. Parsing will determine also that 1040 * the possible (last) valid variable could be 'x', but since it's 1041 * not present in the FOR-variables list, it won't be the case. 1042 * This means that the actual FOR variable character must follow, 1043 * in this case, 'g'. 1044 * 1045 * C:\Temp>for %g in (foo.exe bar.txt) do @echo %~dpnxg 1046 * C:\Temp\foo.exe 1047 * C:\Temp\bar.txt 1048 */ 1049 1050 /* First, go through as many modifier characters as possible */ 1051 FormatEnd = Format = *pFormat; 1052 while (*FormatEnd && _tcschr(ModifierTable, _totlower(*FormatEnd))) 1053 ++FormatEnd; 1054 1055 if (*FormatEnd == _T('$')) 1056 { 1057 /* $PATH: syntax */ 1058 PathVarName = FormatEnd + 1; 1059 FormatEnd = _tcschr(PathVarName, _T(':')); 1060 if (!FormatEnd) 1061 return NULL; 1062 1063 /* Must be immediately followed by the variable */ 1064 if (!GetVar(*++FormatEnd, &Variable, &VariableIsParam0)) 1065 return NULL; 1066 } 1067 else 1068 { 1069 /* Backtrack if necessary to get a variable name match */ 1070 while (!GetVar(*FormatEnd, &Variable, &VariableIsParam0)) 1071 { 1072 if (FormatEnd == Format) 1073 return NULL; 1074 --FormatEnd; 1075 } 1076 } 1077 1078 *pFormat = FormatEnd + 1; 1079 1080 /* If the variable is empty, return an empty string */ 1081 if (!Variable || !*Variable) 1082 return _T(""); 1083 1084 /* Exclude the leading and trailing quotes */ 1085 VarEnd = &Variable[_tcslen(Variable)]; 1086 if (*Variable == _T('"')) 1087 { 1088 ++Variable; 1089 if (VarEnd > Variable && VarEnd[-1] == _T('"')) 1090 --VarEnd; 1091 } 1092 1093 if ((ULONG_PTR)VarEnd - (ULONG_PTR)Variable >= sizeof(Result)) 1094 return _T(""); 1095 memcpy(Result, Variable, (ULONG_PTR)VarEnd - (ULONG_PTR)Variable); 1096 Result[VarEnd - Variable] = _T('\0'); 1097 1098 /* Now determine the actual modifiers */ 1099 for (; Format < FormatEnd && *Format != _T('$'); ++Format) 1100 Modifiers |= 1 << (_tcschr(ModifierTable, _totlower(*Format)) - ModifierTable); 1101 1102 if (PathVarName) 1103 { 1104 /* $PATH: syntax - search the directories listed in the 1105 * specified environment variable for the file */ 1106 PTSTR PathVar; 1107 ((PTSTR)FormatEnd)[-1] = _T('\0'); // FIXME: HACK! 1108 PathVar = GetEnvVar(PathVarName); 1109 ((PTSTR)FormatEnd)[-1] = _T(':'); 1110 if (!PathVar || 1111 !SearchPath(PathVar, Result, NULL, ARRAYSIZE(FullPath), FullPath, NULL)) 1112 { 1113 return _T(""); 1114 } 1115 } 1116 else if (Modifiers == 0) 1117 { 1118 /* For plain %~var with no modifiers, just return the variable without quotes */ 1119 return Result; 1120 } 1121 else if (VariableIsParam0) 1122 { 1123 /* Special case: If the variable is %0 and modifier characters are present, 1124 * use the batch file's path (which includes the .bat/.cmd extension) 1125 * rather than the actual %0 variable (which might not). */ 1126 ASSERT(bc); 1127 _tcscpy(FullPath, bc->BatchFilePath); 1128 } 1129 else 1130 { 1131 /* Convert the variable, now without quotes, to a full path */ 1132 if (!GetFullPathName(Result, ARRAYSIZE(FullPath), FullPath, NULL)) 1133 return _T(""); 1134 } 1135 1136 /* Next step is to change the path to fix letter case (e.g. 1137 * C:\ReAcToS -> C:\ReactOS) and, if requested with the S modifier, 1138 * replace long filenames with short. */ 1139 1140 In = FullPath; 1141 Out = FixedPath; 1142 1143 /* Copy drive letter */ 1144 *Out++ = *In++; 1145 *Out++ = *In++; 1146 *Out++ = *In++; 1147 /* Loop over each \-separated component in the path */ 1148 do { 1149 TCHAR *Next = _tcschr(In, _T('\\')); 1150 if (Next) 1151 *Next++ = _T('\0'); 1152 /* Use FindFirstFile to get the correct name */ 1153 if (Out + _tcslen(In) + 1 >= &FixedPath[ARRAYSIZE(FixedPath)]) 1154 return _T(""); 1155 _tcscpy(Out, In); 1156 hFind = FindFirstFile(FixedPath, &w32fd); 1157 /* If it doesn't exist, just leave the name as it was given */ 1158 if (hFind != INVALID_HANDLE_VALUE) 1159 { 1160 PTSTR FixedComponent = w32fd.cFileName; 1161 if ((Modifiers & M_SHORT) && *w32fd.cAlternateFileName) 1162 FixedComponent = w32fd.cAlternateFileName; 1163 1164 FindClose(hFind); 1165 1166 if (Out + _tcslen(FixedComponent) + 1 >= &FixedPath[ARRAYSIZE(FixedPath)]) 1167 return _T(""); 1168 _tcscpy(Out, FixedComponent); 1169 } 1170 Filename = Out; 1171 Out += _tcslen(Out); 1172 *Out++ = _T('\\'); 1173 1174 In = Next; 1175 } while (In != NULL); 1176 Out[-1] = _T('\0'); 1177 1178 /* Build the result string. Start with attributes, modification time, and 1179 * file size. If the file didn't exist, these fields will all be empty. */ 1180 Out = Result; 1181 if (hFind != INVALID_HANDLE_VALUE) 1182 { 1183 if (Modifiers & M_ATTR) 1184 { 1185 static const struct { 1186 TCHAR Character; 1187 WORD Value; 1188 } *Attrib, Table[] = { 1189 { _T('d'), FILE_ATTRIBUTE_DIRECTORY }, 1190 { _T('r'), FILE_ATTRIBUTE_READONLY }, 1191 { _T('a'), FILE_ATTRIBUTE_ARCHIVE }, 1192 { _T('h'), FILE_ATTRIBUTE_HIDDEN }, 1193 { _T('s'), FILE_ATTRIBUTE_SYSTEM }, 1194 { _T('c'), FILE_ATTRIBUTE_COMPRESSED }, 1195 { _T('o'), FILE_ATTRIBUTE_OFFLINE }, 1196 { _T('t'), FILE_ATTRIBUTE_TEMPORARY }, 1197 { _T('l'), FILE_ATTRIBUTE_REPARSE_POINT }, 1198 #if (NTDDI_VERSION >= NTDDI_WIN8) 1199 { _T('v'), FILE_ATTRIBUTE_INTEGRITY_STREAM }, 1200 { _T('x'), FILE_ATTRIBUTE_NO_SCRUB_DATA /* 0x20000 */ }, 1201 #endif 1202 }; 1203 for (Attrib = Table; Attrib != &Table[ARRAYSIZE(Table)]; Attrib++) 1204 { 1205 *Out++ = w32fd.dwFileAttributes & Attrib->Value 1206 ? Attrib->Character 1207 : _T('-'); 1208 } 1209 *Out++ = _T(' '); 1210 } 1211 if (Modifiers & M_TIME) 1212 { 1213 FILETIME ft; 1214 SYSTEMTIME st; 1215 FileTimeToLocalFileTime(&w32fd.ftLastWriteTime, &ft); 1216 FileTimeToSystemTime(&ft, &st); 1217 1218 Out += FormatDate(Out, &st, TRUE); 1219 *Out++ = _T(' '); 1220 Out += FormatTime(Out, &st); 1221 *Out++ = _T(' '); 1222 } 1223 if (Modifiers & M_SIZE) 1224 { 1225 ULARGE_INTEGER Size; 1226 Size.LowPart = w32fd.nFileSizeLow; 1227 Size.HighPart = w32fd.nFileSizeHigh; 1228 Out += _stprintf(Out, _T("%I64u "), Size.QuadPart); 1229 } 1230 } 1231 1232 /* When using the path-searching syntax or the S modifier, 1233 * at least part of the file path is always included. 1234 * If none of the DPNX modifiers are present, include the full path */ 1235 if (PathVarName || (Modifiers & M_SHORT)) 1236 if ((Modifiers & (M_DRIVE | M_PATH | M_NAME | M_EXT)) == 0) 1237 Modifiers |= M_FULL; 1238 1239 /* Now add the requested parts of the name. 1240 * With the F modifier, add all parts to form the full path. */ 1241 Extension = _tcsrchr(Filename, _T('.')); 1242 if (Modifiers & (M_DRIVE | M_FULL)) 1243 { 1244 *Out++ = FixedPath[0]; 1245 *Out++ = FixedPath[1]; 1246 } 1247 if (Modifiers & (M_PATH | M_FULL)) 1248 { 1249 memcpy(Out, &FixedPath[2], (ULONG_PTR)Filename - (ULONG_PTR)&FixedPath[2]); 1250 Out += Filename - &FixedPath[2]; 1251 } 1252 if (Modifiers & (M_NAME | M_FULL)) 1253 { 1254 while (*Filename && Filename != Extension) 1255 *Out++ = *Filename++; 1256 } 1257 if (Modifiers & (M_EXT | M_FULL)) 1258 { 1259 if (Extension) 1260 Out = _stpcpy(Out, Extension); 1261 } 1262 1263 /* Trim trailing space which otherwise would appear as a 1264 * result of using the A/T/Z modifiers but no others. */ 1265 while (Out != &Result[0] && Out[-1] == _T(' ')) 1266 Out--; 1267 *Out = _T('\0'); 1268 1269 return Result; 1270 } 1271 1272 static PCTSTR 1273 GetBatchVar( 1274 IN PCTSTR varName, 1275 OUT PUINT varNameLen) 1276 { 1277 PCTSTR ret; 1278 PCTSTR varNameEnd; 1279 1280 *varNameLen = 1; 1281 1282 switch (*varName) 1283 { 1284 case _T('~'): 1285 { 1286 varNameEnd = varName + 1; 1287 ret = GetEnhancedVar(&varNameEnd, FindArg); 1288 if (!ret) 1289 { 1290 ParseErrorEx(varName); 1291 return NULL; 1292 } 1293 *varNameLen = varNameEnd - varName; 1294 return ret; 1295 } 1296 1297 case _T('0'): 1298 case _T('1'): 1299 case _T('2'): 1300 case _T('3'): 1301 case _T('4'): 1302 case _T('5'): 1303 case _T('6'): 1304 case _T('7'): 1305 case _T('8'): 1306 case _T('9'): 1307 { 1308 BOOL dummy; 1309 if (!FindArg(*varName, &ret, &dummy)) 1310 return NULL; 1311 else 1312 return ret; 1313 } 1314 1315 case _T('*'): 1316 /* Copy over the raw params (not including the batch file name) */ 1317 return bc->raw_params; 1318 1319 case _T('%'): 1320 return _T("%"); 1321 } 1322 return NULL; 1323 } 1324 1325 BOOL 1326 SubstituteVar( 1327 IN PCTSTR Src, 1328 OUT size_t* SrcIncLen, // VarNameLen 1329 OUT PTCHAR Dest, 1330 IN PTCHAR DestEnd, 1331 OUT size_t* DestIncLen, 1332 IN TCHAR Delim) 1333 { 1334 #define APPEND(From, Length) \ 1335 do { \ 1336 if (Dest + (Length) > DestEnd) \ 1337 goto too_long; \ 1338 memcpy(Dest, (From), (Length) * sizeof(TCHAR)); \ 1339 Dest += (Length); \ 1340 } while (0) 1341 1342 #define APPEND1(Char) \ 1343 do { \ 1344 if (Dest >= DestEnd) \ 1345 goto too_long; \ 1346 *Dest++ = (Char); \ 1347 } while (0) 1348 1349 PCTSTR Var; 1350 PCTSTR Start, End, SubstStart; 1351 TCHAR EndChr; 1352 size_t VarLength; 1353 1354 Start = Src; 1355 End = Dest; 1356 *SrcIncLen = 0; 1357 *DestIncLen = 0; 1358 1359 if (!Delim) 1360 return FALSE; 1361 if (*Src != Delim) 1362 return FALSE; 1363 1364 ++Src; 1365 1366 /* If we are already at the end of the string, fail the substitution */ 1367 SubstStart = Src; 1368 if (!*Src || *Src == _T('\r') || *Src == _T('\n')) 1369 goto bad_subst; 1370 1371 if (bc && Delim == _T('%')) 1372 { 1373 UINT NameLen; 1374 Var = GetBatchVar(Src, &NameLen); 1375 if (!Var && bParseError) 1376 { 1377 /* Return the partially-parsed command to be 1378 * echoed for error diagnostics purposes. */ 1379 APPEND1(Delim); 1380 APPEND(Src, _tcslen(Src) + 1); 1381 return FALSE; 1382 } 1383 if (Var != NULL) 1384 { 1385 VarLength = _tcslen(Var); 1386 APPEND(Var, VarLength); 1387 Src += NameLen; 1388 goto success; 1389 } 1390 } 1391 1392 /* Find the end of the variable name. A colon (:) will usually 1393 * end the name and begin the optional modifier, but not if it 1394 * is immediately followed by the delimiter (%VAR:%). */ 1395 SubstStart = Src; 1396 while (*Src && *Src != Delim && !(*Src == _T(':') && Src[1] != Delim)) 1397 { 1398 ++Src; 1399 } 1400 /* If we are either at the end of the string, or the delimiter 1401 * has been repeated more than once, fail the substitution. */ 1402 if (!*Src || Src == SubstStart) 1403 goto bad_subst; 1404 1405 EndChr = *Src; 1406 *(PTSTR)Src = _T('\0'); // FIXME: HACK! 1407 Var = GetEnvVarOrSpecial(SubstStart); 1408 *(PTSTR)Src++ = EndChr; 1409 if (Var == NULL) 1410 { 1411 /* In a batch context, %NONEXISTENT% "expands" to an 1412 * empty string, otherwise fail the substitution. */ 1413 if (bc) 1414 goto success; 1415 goto bad_subst; 1416 } 1417 VarLength = _tcslen(Var); 1418 1419 if (EndChr == Delim) 1420 { 1421 /* %VAR% - use as-is */ 1422 APPEND(Var, VarLength); 1423 } 1424 else if (*Src == _T('~')) 1425 { 1426 /* %VAR:~[start][,length]% - Substring. 1427 * Negative values are offsets from the end. 1428 */ 1429 SSIZE_T Start = _tcstol(Src + 1, (PTSTR*)&Src, 0); 1430 SSIZE_T End = (SSIZE_T)VarLength; 1431 if (Start < 0) 1432 Start += VarLength; 1433 Start = min(max(Start, 0), VarLength); 1434 if (*Src == _T(',')) 1435 { 1436 End = _tcstol(Src + 1, (PTSTR*)&Src, 0); 1437 End += (End < 0) ? VarLength : Start; 1438 End = min(max(End, Start), VarLength); 1439 } 1440 if (*Src++ != Delim) 1441 goto bad_subst; 1442 APPEND(&Var[Start], End - Start); 1443 } 1444 else 1445 { 1446 /* %VAR:old=new% - Replace all occurrences of old with new. 1447 * %VAR:*old=new% - Replace first occurrence only, 1448 * and remove everything before it. 1449 */ 1450 PCTSTR Old, New; 1451 size_t OldLength, NewLength; 1452 BOOL Star = FALSE; 1453 size_t LastMatch = 0, i = 0; 1454 1455 if (*Src == _T('*')) 1456 { 1457 Star = TRUE; 1458 Src++; 1459 } 1460 1461 /* The string to replace may contain the delimiter */ 1462 Src = _tcschr(Old = Src, _T('=')); 1463 if (Src == NULL) 1464 goto bad_subst; 1465 OldLength = Src++ - Old; 1466 if (OldLength == 0) 1467 goto bad_subst; 1468 1469 Src = _tcschr(New = Src, Delim); 1470 if (Src == NULL) 1471 goto bad_subst; 1472 NewLength = Src++ - New; 1473 1474 while (i < VarLength) 1475 { 1476 if (_tcsnicmp(&Var[i], Old, OldLength) == 0) 1477 { 1478 if (!Star) 1479 APPEND(&Var[LastMatch], i - LastMatch); 1480 APPEND(New, NewLength); 1481 i += OldLength; 1482 LastMatch = i; 1483 if (Star) 1484 break; 1485 continue; 1486 } 1487 i++; 1488 } 1489 APPEND(&Var[LastMatch], VarLength - LastMatch); 1490 } 1491 1492 success: 1493 *SrcIncLen = (Src - Start); 1494 *DestIncLen = (Dest - End); 1495 return TRUE; 1496 1497 bad_subst: 1498 Src = SubstStart; 1499 /* Only if no batch context active do we echo the delimiter */ 1500 if (!bc) 1501 APPEND1(Delim); 1502 goto success; 1503 1504 too_long: 1505 ConOutResPrintf(STRING_ALIAS_ERROR); 1506 nErrorLevel = 9023; 1507 return FALSE; 1508 1509 #undef APPEND 1510 #undef APPEND1 1511 } 1512 1513 BOOL 1514 SubstituteVars( 1515 IN PCTSTR Src, 1516 OUT PTSTR Dest, 1517 IN TCHAR Delim) 1518 { 1519 #define APPEND(From, Length) \ 1520 do { \ 1521 if (Dest + (Length) > DestEnd) \ 1522 goto too_long; \ 1523 memcpy(Dest, (From), (Length) * sizeof(TCHAR)); \ 1524 Dest += (Length); \ 1525 } while (0) 1526 1527 #define APPEND1(Char) \ 1528 do { \ 1529 if (Dest >= DestEnd) \ 1530 goto too_long; \ 1531 *Dest++ = (Char); \ 1532 } while (0) 1533 1534 PTCHAR DestEnd = Dest + CMDLINE_LENGTH - 1; 1535 PCTSTR End; 1536 size_t SrcIncLen, DestIncLen; 1537 1538 while (*Src /* && (Dest < DestEnd) */) 1539 { 1540 if (*Src != Delim) 1541 { 1542 End = _tcschr(Src, Delim); 1543 if (End == NULL) 1544 End = Src + _tcslen(Src); 1545 APPEND(Src, End - Src); 1546 Src = End; 1547 continue; 1548 } 1549 1550 if (!SubstituteVar(Src, &SrcIncLen, Dest, DestEnd, &DestIncLen, Delim)) 1551 { 1552 return FALSE; 1553 } 1554 else 1555 { 1556 Src += SrcIncLen; 1557 Dest += DestIncLen; 1558 } 1559 } 1560 APPEND1(_T('\0')); 1561 return TRUE; 1562 1563 too_long: 1564 ConOutResPrintf(STRING_ALIAS_ERROR); 1565 nErrorLevel = 9023; 1566 return FALSE; 1567 1568 #undef APPEND 1569 #undef APPEND1 1570 } 1571 1572 /* Search the list of FOR contexts for a variable */ 1573 static BOOL 1574 FindForVar( 1575 IN TCHAR Var, 1576 OUT PCTSTR* VarPtr, 1577 OUT BOOL* IsParam0) 1578 { 1579 PFOR_CONTEXT Ctx; 1580 1581 *VarPtr = NULL; 1582 *IsParam0 = FALSE; 1583 1584 for (Ctx = fc; Ctx != NULL; Ctx = Ctx->prev) 1585 { 1586 if ((UINT)(Var - Ctx->firstvar) < Ctx->varcount) 1587 { 1588 *VarPtr = Ctx->values[Var - Ctx->firstvar]; 1589 return TRUE; 1590 } 1591 } 1592 return FALSE; 1593 } 1594 1595 BOOL 1596 SubstituteForVars( 1597 IN PCTSTR Src, 1598 OUT PTSTR Dest) 1599 { 1600 PTCHAR DestEnd = &Dest[CMDLINE_LENGTH - 1]; 1601 while (*Src) 1602 { 1603 if (Src[0] == _T('%')) 1604 { 1605 BOOL Dummy; 1606 PCTSTR End = &Src[2]; 1607 PCTSTR Value = NULL; 1608 1609 if (Src[1] == _T('~')) 1610 Value = GetEnhancedVar(&End, FindForVar); 1611 1612 if (!Value && Src[1]) 1613 { 1614 if (FindForVar(Src[1], &Value, &Dummy) && !Value) 1615 { 1616 /* The variable is empty, return an empty string */ 1617 Value = _T(""); 1618 } 1619 } 1620 1621 if (Value) 1622 { 1623 if (Dest + _tcslen(Value) > DestEnd) 1624 return FALSE; 1625 Dest = _stpcpy(Dest, Value); 1626 Src = End; 1627 continue; 1628 } 1629 } 1630 /* Not a variable; just copy the character */ 1631 if (Dest >= DestEnd) 1632 return FALSE; 1633 *Dest++ = *Src++; 1634 } 1635 *Dest = _T('\0'); 1636 return TRUE; 1637 } 1638 1639 PTSTR 1640 DoDelayedExpansion( 1641 IN PCTSTR Line) 1642 { 1643 TCHAR Buf1[CMDLINE_LENGTH]; 1644 TCHAR Buf2[CMDLINE_LENGTH]; 1645 PTCHAR Src, Dst; 1646 PTCHAR DestEnd = Buf2 + CMDLINE_LENGTH - 1; 1647 size_t SrcIncLen, DestIncLen; 1648 1649 /* First, substitute FOR variables */ 1650 if (!SubstituteForVars(Line, Buf1)) 1651 return NULL; 1652 1653 if (!bDelayedExpansion || !_tcschr(Buf1, _T('!'))) 1654 return cmd_dup(Buf1); 1655 1656 /* 1657 * Delayed substitutions are not actually completely the same as 1658 * immediate substitutions. In particular, it is possible to escape 1659 * the exclamation point using the escape caret. 1660 */ 1661 1662 /* 1663 * Perform delayed expansion: expand variables around '!', 1664 * and reparse escape carets. 1665 */ 1666 1667 #define APPEND1(Char) \ 1668 do { \ 1669 if (Dst >= DestEnd) \ 1670 goto too_long; \ 1671 *Dst++ = (Char); \ 1672 } while (0) 1673 1674 Src = Buf1; 1675 Dst = Buf2; 1676 while (*Src && (Src < &Buf1[CMDLINE_LENGTH])) 1677 { 1678 if (*Src == _T('^')) 1679 { 1680 ++Src; 1681 if (!*Src || !(Src < &Buf1[CMDLINE_LENGTH])) 1682 break; 1683 1684 APPEND1(*Src++); 1685 } 1686 else if (*Src == _T('!')) 1687 { 1688 if (!SubstituteVar(Src, &SrcIncLen, Dst, DestEnd, &DestIncLen, _T('!'))) 1689 { 1690 return NULL; // Got an error during parsing. 1691 } 1692 else 1693 { 1694 Src += SrcIncLen; 1695 Dst += DestIncLen; 1696 } 1697 } 1698 else 1699 { 1700 APPEND1(*Src++); 1701 } 1702 continue; 1703 } 1704 APPEND1(_T('\0')); 1705 1706 return cmd_dup(Buf2); 1707 1708 too_long: 1709 ConOutResPrintf(STRING_ALIAS_ERROR); 1710 nErrorLevel = 9023; 1711 return NULL; 1712 1713 #undef APPEND1 1714 } 1715 1716 1717 /* 1718 * Do the prompt/input/process loop. 1719 */ 1720 BOOL 1721 ReadLine(TCHAR *commandline, BOOL bMore) 1722 { 1723 TCHAR readline[CMDLINE_LENGTH]; 1724 LPTSTR ip; 1725 1726 /* if no batch input then... */ 1727 if (bc == NULL) 1728 { 1729 if (bMore) 1730 { 1731 ConOutResPrintf(STRING_MORE); 1732 } 1733 else 1734 { 1735 /* JPP 19980807 - if echo off, don't print prompt */ 1736 if (bEcho) 1737 { 1738 if (!bIgnoreEcho) 1739 ConOutChar(_T('\n')); 1740 PrintPrompt(); 1741 } 1742 } 1743 1744 if (!ReadCommand(readline, CMDLINE_LENGTH - 1)) 1745 { 1746 bExit = TRUE; 1747 return FALSE; 1748 } 1749 1750 if (readline[0] == _T('\0')) 1751 ConOutChar(_T('\n')); 1752 1753 if (CheckCtrlBreak(BREAK_INPUT)) 1754 return FALSE; 1755 1756 if (readline[0] == _T('\0')) 1757 return FALSE; 1758 1759 ip = readline; 1760 } 1761 else 1762 { 1763 ip = ReadBatchLine(); 1764 if (!ip) 1765 return FALSE; 1766 } 1767 1768 return SubstituteVars(ip, commandline, _T('%')); 1769 } 1770 1771 static INT 1772 ProcessInput(VOID) 1773 { 1774 INT Ret = 0; 1775 PARSED_COMMAND *Cmd; 1776 1777 while (!bCanExit || !bExit) 1778 { 1779 /* Reset the Ctrl-Break / Ctrl-C state */ 1780 bCtrlBreak = FALSE; 1781 1782 Cmd = ParseCommand(NULL); 1783 if (!Cmd) 1784 continue; 1785 1786 Ret = ExecuteCommand(Cmd); 1787 FreeCommand(Cmd); 1788 } 1789 1790 return Ret; 1791 } 1792 1793 1794 /* 1795 * Control-break handler. 1796 */ 1797 static BOOL 1798 WINAPI 1799 BreakHandler(IN DWORD dwCtrlType) 1800 { 1801 DWORD dwWritten; 1802 INPUT_RECORD rec; 1803 1804 if ((dwCtrlType != CTRL_C_EVENT) && 1805 (dwCtrlType != CTRL_BREAK_EVENT)) 1806 { 1807 return FALSE; 1808 } 1809 1810 if (!TryEnterCriticalSection(&ChildProcessRunningLock)) 1811 { 1812 /* Child process is running and will have received the control event */ 1813 return TRUE; 1814 } 1815 else 1816 { 1817 LeaveCriticalSection(&ChildProcessRunningLock); 1818 } 1819 1820 bCtrlBreak = TRUE; 1821 1822 rec.EventType = KEY_EVENT; 1823 rec.Event.KeyEvent.bKeyDown = TRUE; 1824 rec.Event.KeyEvent.wRepeatCount = 1; 1825 rec.Event.KeyEvent.wVirtualKeyCode = _T('C'); 1826 rec.Event.KeyEvent.wVirtualScanCode = _T('C') - 35; 1827 rec.Event.KeyEvent.uChar.AsciiChar = _T('C'); 1828 rec.Event.KeyEvent.uChar.UnicodeChar = _T('C'); 1829 rec.Event.KeyEvent.dwControlKeyState = RIGHT_CTRL_PRESSED; 1830 1831 WriteConsoleInput(ConStreamGetOSHandle(StdIn), 1832 &rec, 1833 1, 1834 &dwWritten); 1835 1836 /* FIXME: Handle batch files */ 1837 1838 // ConOutPrintf(_T("^C")); 1839 1840 return TRUE; 1841 } 1842 1843 1844 VOID AddBreakHandler(VOID) 1845 { 1846 SetConsoleCtrlHandler(BreakHandler, TRUE); 1847 } 1848 1849 1850 VOID RemoveBreakHandler(VOID) 1851 { 1852 SetConsoleCtrlHandler(BreakHandler, FALSE); 1853 } 1854 1855 1856 /* 1857 * Show commands and options that are available. 1858 */ 1859 #if 0 1860 static VOID 1861 ShowCommands(VOID) 1862 { 1863 /* print command list */ 1864 ConOutResPuts(STRING_CMD_HELP1); 1865 PrintCommandList(); 1866 1867 /* print feature list */ 1868 ConOutResPuts(STRING_CMD_HELP2); 1869 1870 #ifdef FEATURE_ALIASES 1871 ConOutResPuts(STRING_CMD_HELP3); 1872 #endif 1873 #ifdef FEATURE_HISTORY 1874 ConOutResPuts(STRING_CMD_HELP4); 1875 #endif 1876 #ifdef FEATURE_UNIX_FILENAME_COMPLETION 1877 ConOutResPuts(STRING_CMD_HELP5); 1878 #endif 1879 #ifdef FEATURE_DIRECTORY_STACK 1880 ConOutResPuts(STRING_CMD_HELP6); 1881 #endif 1882 #ifdef FEATURE_REDIRECTION 1883 ConOutResPuts(STRING_CMD_HELP7); 1884 #endif 1885 ConOutChar(_T('\n')); 1886 } 1887 #endif 1888 1889 1890 static VOID 1891 LoadRegistrySettings(HKEY hKeyRoot) 1892 { 1893 LONG lRet; 1894 HKEY hKey; 1895 DWORD dwType, len; 1896 /* 1897 * Buffer big enough to hold the string L"4294967295", 1898 * corresponding to the literal 0xFFFFFFFF (MAXULONG) in decimal. 1899 */ 1900 DWORD Buffer[6]; 1901 1902 lRet = RegOpenKeyEx(hKeyRoot, 1903 _T("Software\\Microsoft\\Command Processor"), 1904 0, 1905 KEY_QUERY_VALUE, 1906 &hKey); 1907 if (lRet != ERROR_SUCCESS) 1908 return; 1909 1910 #ifdef INCLUDE_CMD_COLOR 1911 len = sizeof(Buffer); 1912 lRet = RegQueryValueEx(hKey, 1913 _T("DefaultColor"), 1914 NULL, 1915 &dwType, 1916 (LPBYTE)&Buffer, 1917 &len); 1918 if (lRet == ERROR_SUCCESS) 1919 { 1920 /* Overwrite the default attributes */ 1921 if (dwType == REG_DWORD) 1922 wDefColor = (WORD)*(PDWORD)Buffer; 1923 else if (dwType == REG_SZ) 1924 wDefColor = (WORD)_tcstol((PTSTR)Buffer, NULL, 0); 1925 } 1926 // else, use the default attributes retrieved before. 1927 #endif 1928 1929 #if 0 1930 len = sizeof(Buffer); 1931 lRet = RegQueryValueEx(hKey, 1932 _T("DisableUNCCheck"), 1933 NULL, 1934 &dwType, 1935 (LPBYTE)&Buffer, 1936 &len); 1937 if (lRet == ERROR_SUCCESS) 1938 { 1939 /* Overwrite the default setting */ 1940 if (dwType == REG_DWORD) 1941 bDisableUNCCheck = !!*(PDWORD)Buffer; 1942 else if (dwType == REG_SZ) 1943 bDisableUNCCheck = (_ttol((PTSTR)Buffer) == 1); 1944 } 1945 // else, use the default setting set globally. 1946 #endif 1947 1948 len = sizeof(Buffer); 1949 lRet = RegQueryValueEx(hKey, 1950 _T("DelayedExpansion"), 1951 NULL, 1952 &dwType, 1953 (LPBYTE)&Buffer, 1954 &len); 1955 if (lRet == ERROR_SUCCESS) 1956 { 1957 /* Overwrite the default setting */ 1958 if (dwType == REG_DWORD) 1959 bDelayedExpansion = !!*(PDWORD)Buffer; 1960 else if (dwType == REG_SZ) 1961 bDelayedExpansion = (_ttol((PTSTR)Buffer) == 1); 1962 } 1963 // else, use the default setting set globally. 1964 1965 len = sizeof(Buffer); 1966 lRet = RegQueryValueEx(hKey, 1967 _T("EnableExtensions"), 1968 NULL, 1969 &dwType, 1970 (LPBYTE)&Buffer, 1971 &len); 1972 if (lRet == ERROR_SUCCESS) 1973 { 1974 /* Overwrite the default setting */ 1975 if (dwType == REG_DWORD) 1976 bEnableExtensions = !!*(PDWORD)Buffer; 1977 else if (dwType == REG_SZ) 1978 bEnableExtensions = (_ttol((PTSTR)Buffer) == 1); 1979 } 1980 // else, use the default setting set globally. 1981 1982 len = sizeof(Buffer); 1983 lRet = RegQueryValueEx(hKey, 1984 _T("CompletionChar"), 1985 NULL, 1986 &dwType, 1987 (LPBYTE)&Buffer, 1988 &len); 1989 if (lRet == ERROR_SUCCESS) 1990 { 1991 /* Overwrite the default setting */ 1992 if (dwType == REG_DWORD) 1993 AutoCompletionChar = (TCHAR)*(PDWORD)Buffer; 1994 else if (dwType == REG_SZ) 1995 AutoCompletionChar = (TCHAR)_tcstol((PTSTR)Buffer, NULL, 0); 1996 } 1997 // else, use the default setting set globally. 1998 1999 /* Validity check */ 2000 if (IS_COMPLETION_DISABLED(AutoCompletionChar)) 2001 { 2002 /* Disable autocompletion */ 2003 AutoCompletionChar = 0x20; 2004 } 2005 2006 len = sizeof(Buffer); 2007 lRet = RegQueryValueEx(hKey, 2008 _T("PathCompletionChar"), 2009 NULL, 2010 &dwType, 2011 (LPBYTE)&Buffer, 2012 &len); 2013 if (lRet == ERROR_SUCCESS) 2014 { 2015 /* Overwrite the default setting */ 2016 if (dwType == REG_DWORD) 2017 PathCompletionChar = (TCHAR)*(PDWORD)Buffer; 2018 else if (dwType == REG_SZ) 2019 PathCompletionChar = (TCHAR)_tcstol((PTSTR)Buffer, NULL, 0); 2020 } 2021 // else, use the default setting set globally. 2022 2023 /* Validity check */ 2024 if (IS_COMPLETION_DISABLED(PathCompletionChar)) 2025 { 2026 /* Disable autocompletion */ 2027 PathCompletionChar = 0x20; 2028 } 2029 2030 /* Adjust completion chars */ 2031 if (PathCompletionChar >= 0x20 && AutoCompletionChar < 0x20) 2032 PathCompletionChar = AutoCompletionChar; 2033 else if (AutoCompletionChar >= 0x20 && PathCompletionChar < 0x20) 2034 AutoCompletionChar = PathCompletionChar; 2035 2036 RegCloseKey(hKey); 2037 } 2038 2039 static VOID 2040 ExecuteAutoRunFile(HKEY hKeyRoot) 2041 { 2042 LONG lRet; 2043 HKEY hKey; 2044 DWORD dwType, len; 2045 TCHAR AutoRun[2048]; 2046 2047 lRet = RegOpenKeyEx(hKeyRoot, 2048 _T("Software\\Microsoft\\Command Processor"), 2049 0, 2050 KEY_QUERY_VALUE, 2051 &hKey); 2052 if (lRet != ERROR_SUCCESS) 2053 return; 2054 2055 len = sizeof(AutoRun); 2056 lRet = RegQueryValueEx(hKey, 2057 _T("AutoRun"), 2058 NULL, 2059 &dwType, 2060 (LPBYTE)&AutoRun, 2061 &len); 2062 if ((lRet == ERROR_SUCCESS) && (dwType == REG_EXPAND_SZ || dwType == REG_SZ)) 2063 { 2064 if (*AutoRun) 2065 ParseCommandLine(AutoRun); 2066 } 2067 2068 RegCloseKey(hKey); 2069 } 2070 2071 /* Get the command that comes after a /C or /K switch */ 2072 static VOID 2073 GetCmdLineCommand( 2074 OUT LPTSTR commandline, 2075 IN LPCTSTR ptr, 2076 IN BOOL AlwaysStrip) 2077 { 2078 TCHAR* LastQuote; 2079 2080 while (_istspace(*ptr)) 2081 ++ptr; 2082 2083 /* Remove leading quote, find final quote */ 2084 if (*ptr == _T('"') && 2085 (LastQuote = _tcsrchr(++ptr, _T('"'))) != NULL) 2086 { 2087 const TCHAR* Space; 2088 /* Under certain circumstances, all quotes are preserved. 2089 * CMD /? documents these conditions as follows: 2090 * 1. No /S switch 2091 * 2. Exactly two quotes 2092 * 3. No "special characters" between the quotes 2093 * (CMD /? says &<>()@^| but parentheses did not 2094 * trigger this rule when I tested them.) 2095 * 4. Whitespace exists between the quotes 2096 * 5. Enclosed string is an executable filename 2097 */ 2098 *LastQuote = _T('\0'); 2099 for (Space = ptr + 1; Space < LastQuote; ++Space) 2100 { 2101 if (_istspace(*Space)) /* Rule 4 */ 2102 { 2103 if (!AlwaysStrip && /* Rule 1 */ 2104 !_tcspbrk(ptr, _T("\"&<>@^|")) && /* Rules 2, 3 */ 2105 SearchForExecutable(ptr, commandline)) /* Rule 5 */ 2106 { 2107 /* All conditions met: preserve both the quotes */ 2108 *LastQuote = _T('"'); 2109 _tcscpy(commandline, ptr - 1); 2110 return; 2111 } 2112 break; 2113 } 2114 } 2115 2116 /* The conditions were not met: remove both the 2117 * leading quote and the last quote */ 2118 _tcscpy(commandline, ptr); 2119 _tcscpy(&commandline[LastQuote - ptr], LastQuote + 1); 2120 return; 2121 } 2122 2123 /* No quotes; just copy */ 2124 _tcscpy(commandline, ptr); 2125 } 2126 2127 2128 /* 2129 * Set up global initializations and process parameters. 2130 * Return a pointer to the command line if present. 2131 */ 2132 static LPCTSTR 2133 Initialize(VOID) 2134 { 2135 HMODULE NtDllModule; 2136 HANDLE hIn, hOut; 2137 LPTSTR ptr, cmdLine; 2138 TCHAR option = 0; 2139 BOOL AutoRun = TRUE; 2140 TCHAR ModuleName[MAX_PATH + 1]; 2141 2142 /* Get version information */ 2143 InitOSVersion(); 2144 2145 /* Some people like to run ReactOS cmd.exe on Win98, it helps in the 2146 * build process. So don't link implicitly against ntdll.dll, load it 2147 * dynamically instead */ 2148 NtDllModule = GetModuleHandle(TEXT("ntdll.dll")); 2149 if (NtDllModule != NULL) 2150 { 2151 NtQueryInformationProcessPtr = (NtQueryInformationProcessProc)GetProcAddress(NtDllModule, "NtQueryInformationProcess"); 2152 NtReadVirtualMemoryPtr = (NtReadVirtualMemoryProc)GetProcAddress(NtDllModule, "NtReadVirtualMemory"); 2153 } 2154 2155 /* Load the registry settings */ 2156 LoadRegistrySettings(HKEY_LOCAL_MACHINE); 2157 LoadRegistrySettings(HKEY_CURRENT_USER); 2158 2159 /* Initialize our locale */ 2160 InitLocale(); 2161 2162 /* Initialize prompt support */ 2163 InitPrompt(); 2164 2165 #ifdef FEATURE_DIRECTORY_STACK 2166 /* Initialize directory stack */ 2167 InitDirectoryStack(); 2168 #endif 2169 2170 #ifdef FEATURE_HISTORY 2171 /* Initialize history */ 2172 InitHistory(); 2173 #endif 2174 2175 /* Set COMSPEC environment variable */ 2176 if (GetModuleFileName(NULL, ModuleName, ARRAYSIZE(ModuleName)) != 0) 2177 { 2178 ModuleName[MAX_PATH] = _T('\0'); 2179 SetEnvironmentVariable (_T("COMSPEC"), ModuleName); 2180 } 2181 2182 /* Add ctrl break handler */ 2183 AddBreakHandler(); 2184 2185 /* Set the default console mode */ 2186 hOut = ConStreamGetOSHandle(StdOut); 2187 hIn = ConStreamGetOSHandle(StdIn); 2188 SetConsoleMode(hOut, 0); // Reinitialize the console output mode 2189 SetConsoleMode(hOut, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT); 2190 SetConsoleMode(hIn , ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); 2191 2192 cmdLine = GetCommandLine(); 2193 TRACE ("[command args: %s]\n", debugstr_aw(cmdLine)); 2194 2195 for (ptr = cmdLine; *ptr; ++ptr) 2196 { 2197 if (*ptr == _T('/')) 2198 { 2199 option = _totupper(ptr[1]); 2200 if (option == _T('?')) 2201 { 2202 ConOutResPaging(TRUE, STRING_CMD_HELP8); 2203 nErrorLevel = 1; 2204 bExit = TRUE; 2205 return NULL; 2206 } 2207 else if (option == _T('P')) 2208 { 2209 if (!IsExistingFile(_T("\\autoexec.bat"))) 2210 { 2211 #ifdef INCLUDE_CMD_DATE 2212 cmd_date(_T("")); 2213 #endif 2214 #ifdef INCLUDE_CMD_TIME 2215 cmd_time(_T("")); 2216 #endif 2217 } 2218 else 2219 { 2220 ParseCommandLine(_T("\\autoexec.bat")); 2221 } 2222 bCanExit = FALSE; 2223 } 2224 else if (option == _T('A')) 2225 { 2226 OutputStreamMode = AnsiText; 2227 } 2228 else if (option == _T('C') || option == _T('K') || option == _T('R')) 2229 { 2230 /* Remainder of command line is a command to be run */ 2231 fSingleCommand = ((option == _T('K')) << 1) | 1; 2232 break; 2233 } 2234 else if (option == _T('D')) 2235 { 2236 AutoRun = FALSE; 2237 } 2238 else if (option == _T('Q')) 2239 { 2240 bDisableBatchEcho = TRUE; 2241 } 2242 else if (option == _T('S')) 2243 { 2244 bAlwaysStrip = TRUE; 2245 } 2246 #ifdef INCLUDE_CMD_COLOR 2247 else if (!_tcsnicmp(ptr, _T("/T:"), 3)) 2248 { 2249 /* Process /T (color) argument; overwrite any previous settings */ 2250 wDefColor = (WORD)_tcstoul(&ptr[3], &ptr, 16); 2251 } 2252 #endif 2253 else if (option == _T('U')) 2254 { 2255 OutputStreamMode = UTF16Text; 2256 } 2257 else if (option == _T('F')) 2258 { 2259 if (!_tcsnicmp(&ptr[2], _T(":OFF"), 4)) 2260 { 2261 /* Disable file and path completion */ 2262 AutoCompletionChar = 0x20; 2263 PathCompletionChar = 0x20; 2264 } 2265 else /* Enable completion by default */ 2266 { 2267 /* Enable (and replace) file and path completion 2268 * characters with Ctrl-F and Ctrl-D respectively */ 2269 AutoCompletionChar = 0x06; // Ctrl-F 2270 PathCompletionChar = 0x04; // Ctrl-D 2271 } 2272 } 2273 else if (option == _T('V')) 2274 { 2275 // FIXME: Check validity of the parameter given to V ! 2276 bDelayedExpansion = _tcsnicmp(&ptr[2], _T(":OFF"), 4); 2277 } 2278 else if (option == _T('E')) 2279 { 2280 // FIXME: Check validity of the parameter given to E ! 2281 bEnableExtensions = _tcsnicmp(&ptr[2], _T(":OFF"), 4); 2282 } 2283 else if (option == _T('X')) 2284 { 2285 /* '/X' is identical to '/E:ON' */ 2286 bEnableExtensions = TRUE; 2287 } 2288 else if (option == _T('Y')) 2289 { 2290 /* '/Y' is identical to '/E:OFF' */ 2291 bEnableExtensions = FALSE; 2292 } 2293 } 2294 } 2295 2296 #ifdef INCLUDE_CMD_COLOR 2297 if (wDefColor == 0) 2298 { 2299 /* 2300 * If we still do not have the console colour attribute set, 2301 * retrieve the default one. 2302 */ 2303 ConGetDefaultAttributes(&wDefColor); 2304 } 2305 2306 if (wDefColor != 0) 2307 ConSetScreenColor(ConStreamGetOSHandle(StdOut), wDefColor, TRUE); 2308 #endif 2309 2310 /* Reset the output Standard Streams translation modes and code page caches */ 2311 // ConStreamSetMode(StdIn , OutputStreamMode, InputCodePage ); 2312 ConStreamSetMode(StdOut, OutputStreamMode, OutputCodePage); 2313 ConStreamSetMode(StdErr, OutputStreamMode, OutputCodePage); 2314 2315 if (!*ptr) 2316 { 2317 /* If neither /C or /K was given, display a simple version string */ 2318 2319 /* Insert a new line above the copyright notice if we are drawing the information line. */ 2320 if (HasInfoLine()) 2321 ConOutChar('\n'); 2322 2323 ConOutResPrintf(STRING_REACTOS_VERSION, 2324 _T(KERNEL_VERSION_STR), 2325 _T(KERNEL_VERSION_BUILD_STR)); 2326 ConOutResPrintf(STRING_CMD_COPYRIGHT, _T(COPYRIGHT_YEAR)); 2327 } 2328 2329 if (AutoRun) 2330 { 2331 ExecuteAutoRunFile(HKEY_LOCAL_MACHINE); 2332 ExecuteAutoRunFile(HKEY_CURRENT_USER); 2333 } 2334 2335 /* Returns the rest of the command line */ 2336 return ptr; 2337 } 2338 2339 2340 static VOID Cleanup(VOID) 2341 { 2342 /* Run cmdexit.bat */ 2343 if (IsExistingFile(_T("cmdexit.bat"))) 2344 { 2345 ConErrResPuts(STRING_CMD_ERROR5); 2346 ParseCommandLine(_T("cmdexit.bat")); 2347 } 2348 else if (IsExistingFile(_T("\\cmdexit.bat"))) 2349 { 2350 ConErrResPuts(STRING_CMD_ERROR5); 2351 ParseCommandLine(_T("\\cmdexit.bat")); 2352 } 2353 2354 /* Remove ctrl break handler */ 2355 RemoveBreakHandler(); 2356 2357 /* Restore the default console mode */ 2358 SetConsoleMode(ConStreamGetOSHandle(StdIn), 2359 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); 2360 SetConsoleMode(ConStreamGetOSHandle(StdOut), 2361 ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT); 2362 2363 2364 #ifdef _DEBUG_MEM 2365 #ifdef FEATURE_DIRECTORY_STACK 2366 /* Destroy directory stack */ 2367 DestroyDirectoryStack(); 2368 #endif 2369 2370 #ifdef FEATURE_HISTORY 2371 CleanHistory(); 2372 #endif 2373 2374 /* Free GetEnvVar's buffer */ 2375 GetEnvVar(NULL); 2376 #endif /* _DEBUG_MEM */ 2377 2378 DeleteCriticalSection(&ChildProcessRunningLock); 2379 } 2380 2381 /* 2382 * main function 2383 */ 2384 int _tmain(int argc, const TCHAR *argv[]) 2385 { 2386 INT nExitCode; 2387 LPCTSTR pCmdLine; 2388 TCHAR startPath[MAX_PATH]; 2389 2390 InitializeCriticalSection(&ChildProcessRunningLock); 2391 lpOriginalEnvironment = DuplicateEnvironment(); 2392 2393 GetCurrentDirectory(ARRAYSIZE(startPath), startPath); 2394 _tchdir(startPath); 2395 2396 SetFileApisToOEM(); 2397 InputCodePage = GetConsoleCP(); 2398 OutputCodePage = GetConsoleOutputCP(); 2399 2400 /* Initialize the Console Standard Streams */ 2401 ConStreamInit(StdIn , GetStdHandle(STD_INPUT_HANDLE) , /*OutputStreamMode*/ AnsiText, InputCodePage); 2402 ConStreamInit(StdOut, GetStdHandle(STD_OUTPUT_HANDLE), OutputStreamMode, OutputCodePage); 2403 ConStreamInit(StdErr, GetStdHandle(STD_ERROR_HANDLE) , OutputStreamMode, OutputCodePage); 2404 /* Reset the current thread UI language */ 2405 if (IsConsoleHandle(ConStreamGetOSHandle(StdOut)) || 2406 IsConsoleHandle(ConStreamGetOSHandle(StdErr))) 2407 { 2408 ConSetThreadUILanguage(0); 2409 } 2410 2411 CMD_ModuleHandle = GetModuleHandle(NULL); 2412 2413 /* 2414 * Perform general initialization, parse switches on command-line. 2415 * Initialize the exit code with the errorlevel as Initialize() can set it. 2416 */ 2417 pCmdLine = Initialize(); 2418 nExitCode = nErrorLevel; 2419 2420 if (pCmdLine && *pCmdLine) 2421 { 2422 TCHAR commandline[CMDLINE_LENGTH]; 2423 2424 /* Do the /C or /K command */ 2425 GetCmdLineCommand(commandline, &pCmdLine[2], bAlwaysStrip); 2426 nExitCode = ParseCommandLine(commandline); 2427 if (fSingleCommand == 1) 2428 { 2429 // nErrorLevel = nExitCode; 2430 bExit = TRUE; 2431 } 2432 fSingleCommand = 0; 2433 } 2434 if (!bExit) 2435 { 2436 /* Call prompt routine */ 2437 nExitCode = ProcessInput(); 2438 } 2439 2440 /* Do the cleanup */ 2441 Cleanup(); 2442 cmd_free(lpOriginalEnvironment); 2443 2444 cmd_exit(nExitCode); 2445 return nExitCode; 2446 } 2447 2448 /* EOF */ 2449