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 (*w32fd.cAlternateFileName && 1162 ((Modifiers & M_SHORT) || !_tcsicmp(In, w32fd.cAlternateFileName))) 1163 { 1164 FixedComponent = w32fd.cAlternateFileName; 1165 } 1166 FindClose(hFind); 1167 1168 if (Out + _tcslen(FixedComponent) + 1 >= &FixedPath[ARRAYSIZE(FixedPath)]) 1169 return _T(""); 1170 _tcscpy(Out, FixedComponent); 1171 } 1172 Filename = Out; 1173 Out += _tcslen(Out); 1174 *Out++ = _T('\\'); 1175 1176 In = Next; 1177 } while (In != NULL); 1178 Out[-1] = _T('\0'); 1179 1180 /* Build the result string. Start with attributes, modification time, and 1181 * file size. If the file didn't exist, these fields will all be empty. */ 1182 Out = Result; 1183 if (hFind != INVALID_HANDLE_VALUE) 1184 { 1185 if (Modifiers & M_ATTR) 1186 { 1187 static const struct { 1188 TCHAR Character; 1189 WORD Value; 1190 } *Attrib, Table[] = { 1191 { _T('d'), FILE_ATTRIBUTE_DIRECTORY }, 1192 { _T('r'), FILE_ATTRIBUTE_READONLY }, 1193 { _T('a'), FILE_ATTRIBUTE_ARCHIVE }, 1194 { _T('h'), FILE_ATTRIBUTE_HIDDEN }, 1195 { _T('s'), FILE_ATTRIBUTE_SYSTEM }, 1196 { _T('c'), FILE_ATTRIBUTE_COMPRESSED }, 1197 { _T('o'), FILE_ATTRIBUTE_OFFLINE }, 1198 { _T('t'), FILE_ATTRIBUTE_TEMPORARY }, 1199 { _T('l'), FILE_ATTRIBUTE_REPARSE_POINT }, 1200 #if (NTDDI_VERSION >= NTDDI_WIN8) 1201 { _T('v'), FILE_ATTRIBUTE_INTEGRITY_STREAM }, 1202 { _T('x'), FILE_ATTRIBUTE_NO_SCRUB_DATA /* 0x20000 */ }, 1203 #endif 1204 }; 1205 for (Attrib = Table; Attrib != &Table[ARRAYSIZE(Table)]; Attrib++) 1206 { 1207 *Out++ = w32fd.dwFileAttributes & Attrib->Value 1208 ? Attrib->Character 1209 : _T('-'); 1210 } 1211 *Out++ = _T(' '); 1212 } 1213 if (Modifiers & M_TIME) 1214 { 1215 FILETIME ft; 1216 SYSTEMTIME st; 1217 FileTimeToLocalFileTime(&w32fd.ftLastWriteTime, &ft); 1218 FileTimeToSystemTime(&ft, &st); 1219 1220 Out += FormatDate(Out, &st, TRUE); 1221 *Out++ = _T(' '); 1222 Out += FormatTime(Out, &st); 1223 *Out++ = _T(' '); 1224 } 1225 if (Modifiers & M_SIZE) 1226 { 1227 ULARGE_INTEGER Size; 1228 Size.LowPart = w32fd.nFileSizeLow; 1229 Size.HighPart = w32fd.nFileSizeHigh; 1230 Out += _stprintf(Out, _T("%I64u "), Size.QuadPart); 1231 } 1232 } 1233 1234 /* When using the path-searching syntax or the S modifier, 1235 * at least part of the file path is always included. 1236 * If none of the DPNX modifiers are present, include the full path */ 1237 if (PathVarName || (Modifiers & M_SHORT)) 1238 if ((Modifiers & (M_DRIVE | M_PATH | M_NAME | M_EXT)) == 0) 1239 Modifiers |= M_FULL; 1240 1241 /* Now add the requested parts of the name. 1242 * With the F modifier, add all parts to form the full path. */ 1243 Extension = _tcsrchr(Filename, _T('.')); 1244 if (Modifiers & (M_DRIVE | M_FULL)) 1245 { 1246 *Out++ = FixedPath[0]; 1247 *Out++ = FixedPath[1]; 1248 } 1249 if (Modifiers & (M_PATH | M_FULL)) 1250 { 1251 memcpy(Out, &FixedPath[2], (ULONG_PTR)Filename - (ULONG_PTR)&FixedPath[2]); 1252 Out += Filename - &FixedPath[2]; 1253 } 1254 if (Modifiers & (M_NAME | M_FULL)) 1255 { 1256 while (*Filename && Filename != Extension) 1257 *Out++ = *Filename++; 1258 } 1259 if (Modifiers & (M_EXT | M_FULL)) 1260 { 1261 if (Extension) 1262 Out = _stpcpy(Out, Extension); 1263 } 1264 1265 /* Trim trailing space which otherwise would appear as a 1266 * result of using the A/T/Z modifiers but no others. */ 1267 while (Out != &Result[0] && Out[-1] == _T(' ')) 1268 Out--; 1269 *Out = _T('\0'); 1270 1271 return Result; 1272 } 1273 1274 static PCTSTR 1275 GetBatchVar( 1276 IN PCTSTR varName, 1277 OUT PUINT varNameLen) 1278 { 1279 PCTSTR ret; 1280 PCTSTR varNameEnd; 1281 1282 *varNameLen = 1; 1283 1284 switch (*varName) 1285 { 1286 case _T('~'): 1287 { 1288 varNameEnd = varName + 1; 1289 ret = GetEnhancedVar(&varNameEnd, FindArg); 1290 if (!ret) 1291 { 1292 ParseErrorEx(varName); 1293 return NULL; 1294 } 1295 *varNameLen = varNameEnd - varName; 1296 return ret; 1297 } 1298 1299 case _T('0'): 1300 case _T('1'): 1301 case _T('2'): 1302 case _T('3'): 1303 case _T('4'): 1304 case _T('5'): 1305 case _T('6'): 1306 case _T('7'): 1307 case _T('8'): 1308 case _T('9'): 1309 { 1310 BOOL dummy; 1311 if (!FindArg(*varName, &ret, &dummy)) 1312 return NULL; 1313 else 1314 return ret; 1315 } 1316 1317 case _T('*'): 1318 /* Copy over the raw params (not including the batch file name) */ 1319 return bc->raw_params; 1320 1321 case _T('%'): 1322 return _T("%"); 1323 } 1324 return NULL; 1325 } 1326 1327 BOOL 1328 SubstituteVar( 1329 IN PCTSTR Src, 1330 OUT size_t* SrcIncLen, // VarNameLen 1331 OUT PTCHAR Dest, 1332 IN PTCHAR DestEnd, 1333 OUT size_t* DestIncLen, 1334 IN TCHAR Delim) 1335 { 1336 #define APPEND(From, Length) \ 1337 do { \ 1338 if (Dest + (Length) > DestEnd) \ 1339 goto too_long; \ 1340 memcpy(Dest, (From), (Length) * sizeof(TCHAR)); \ 1341 Dest += (Length); \ 1342 } while (0) 1343 1344 #define APPEND1(Char) \ 1345 do { \ 1346 if (Dest >= DestEnd) \ 1347 goto too_long; \ 1348 *Dest++ = (Char); \ 1349 } while (0) 1350 1351 PCTSTR Var; 1352 PCTSTR Start, End, SubstStart; 1353 TCHAR EndChr; 1354 size_t VarLength; 1355 1356 Start = Src; 1357 End = Dest; 1358 *SrcIncLen = 0; 1359 *DestIncLen = 0; 1360 1361 if (!Delim) 1362 return FALSE; 1363 if (*Src != Delim) 1364 return FALSE; 1365 1366 ++Src; 1367 1368 /* If we are already at the end of the string, fail the substitution */ 1369 SubstStart = Src; 1370 if (!*Src || *Src == _T('\r') || *Src == _T('\n')) 1371 goto bad_subst; 1372 1373 if (bc && Delim == _T('%')) 1374 { 1375 UINT NameLen; 1376 Var = GetBatchVar(Src, &NameLen); 1377 if (!Var && bParseError) 1378 { 1379 /* Return the partially-parsed command to be 1380 * echoed for error diagnostics purposes. */ 1381 APPEND1(Delim); 1382 APPEND(Src, _tcslen(Src) + 1); 1383 return FALSE; 1384 } 1385 if (Var != NULL) 1386 { 1387 VarLength = _tcslen(Var); 1388 APPEND(Var, VarLength); 1389 Src += NameLen; 1390 goto success; 1391 } 1392 } 1393 1394 /* Find the end of the variable name. A colon (:) will usually 1395 * end the name and begin the optional modifier, but not if it 1396 * is immediately followed by the delimiter (%VAR:%). */ 1397 SubstStart = Src; 1398 while (*Src && *Src != Delim && !(*Src == _T(':') && Src[1] != Delim)) 1399 { 1400 ++Src; 1401 } 1402 /* If we are either at the end of the string, or the delimiter 1403 * has been repeated more than once, fail the substitution. */ 1404 if (!*Src || Src == SubstStart) 1405 goto bad_subst; 1406 1407 EndChr = *Src; 1408 *(PTSTR)Src = _T('\0'); // FIXME: HACK! 1409 Var = GetEnvVarOrSpecial(SubstStart); 1410 *(PTSTR)Src++ = EndChr; 1411 if (Var == NULL) 1412 { 1413 /* In a batch context, %NONEXISTENT% "expands" to an 1414 * empty string, otherwise fail the substitution. */ 1415 if (bc) 1416 goto success; 1417 goto bad_subst; 1418 } 1419 VarLength = _tcslen(Var); 1420 1421 if (EndChr == Delim) 1422 { 1423 /* %VAR% - use as-is */ 1424 APPEND(Var, VarLength); 1425 } 1426 else if (*Src == _T('~')) 1427 { 1428 /* %VAR:~[start][,length]% - Substring. 1429 * Negative values are offsets from the end. 1430 */ 1431 SSIZE_T Start = _tcstol(Src + 1, (PTSTR*)&Src, 0); 1432 SSIZE_T End = (SSIZE_T)VarLength; 1433 if (Start < 0) 1434 Start += VarLength; 1435 Start = min(max(Start, 0), VarLength); 1436 if (*Src == _T(',')) 1437 { 1438 End = _tcstol(Src + 1, (PTSTR*)&Src, 0); 1439 End += (End < 0) ? VarLength : Start; 1440 End = min(max(End, Start), VarLength); 1441 } 1442 if (*Src++ != Delim) 1443 goto bad_subst; 1444 APPEND(&Var[Start], End - Start); 1445 } 1446 else 1447 { 1448 /* %VAR:old=new% - Replace all occurrences of old with new. 1449 * %VAR:*old=new% - Replace first occurrence only, 1450 * and remove everything before it. 1451 */ 1452 PCTSTR Old, New; 1453 size_t OldLength, NewLength; 1454 BOOL Star = FALSE; 1455 size_t LastMatch = 0, i = 0; 1456 1457 if (*Src == _T('*')) 1458 { 1459 Star = TRUE; 1460 Src++; 1461 } 1462 1463 /* The string to replace may contain the delimiter */ 1464 Src = _tcschr(Old = Src, _T('=')); 1465 if (Src == NULL) 1466 goto bad_subst; 1467 OldLength = Src++ - Old; 1468 if (OldLength == 0) 1469 goto bad_subst; 1470 1471 Src = _tcschr(New = Src, Delim); 1472 if (Src == NULL) 1473 goto bad_subst; 1474 NewLength = Src++ - New; 1475 1476 while (i < VarLength) 1477 { 1478 if (_tcsnicmp(&Var[i], Old, OldLength) == 0) 1479 { 1480 if (!Star) 1481 APPEND(&Var[LastMatch], i - LastMatch); 1482 APPEND(New, NewLength); 1483 i += OldLength; 1484 LastMatch = i; 1485 if (Star) 1486 break; 1487 continue; 1488 } 1489 i++; 1490 } 1491 APPEND(&Var[LastMatch], VarLength - LastMatch); 1492 } 1493 1494 success: 1495 *SrcIncLen = (Src - Start); 1496 *DestIncLen = (Dest - End); 1497 return TRUE; 1498 1499 bad_subst: 1500 Src = SubstStart; 1501 /* Only if no batch context active do we echo the delimiter */ 1502 if (!bc) 1503 APPEND1(Delim); 1504 goto success; 1505 1506 too_long: 1507 ConOutResPrintf(STRING_ALIAS_ERROR); 1508 nErrorLevel = 9023; 1509 return FALSE; 1510 1511 #undef APPEND 1512 #undef APPEND1 1513 } 1514 1515 BOOL 1516 SubstituteVars( 1517 IN PCTSTR Src, 1518 OUT PTSTR Dest, 1519 IN TCHAR Delim) 1520 { 1521 #define APPEND(From, Length) \ 1522 do { \ 1523 if (Dest + (Length) > DestEnd) \ 1524 goto too_long; \ 1525 memcpy(Dest, (From), (Length) * sizeof(TCHAR)); \ 1526 Dest += (Length); \ 1527 } while (0) 1528 1529 #define APPEND1(Char) \ 1530 do { \ 1531 if (Dest >= DestEnd) \ 1532 goto too_long; \ 1533 *Dest++ = (Char); \ 1534 } while (0) 1535 1536 PTCHAR DestEnd = Dest + CMDLINE_LENGTH - 1; 1537 PCTSTR End; 1538 size_t SrcIncLen, DestIncLen; 1539 1540 while (*Src /* && (Dest < DestEnd) */) 1541 { 1542 if (*Src != Delim) 1543 { 1544 End = _tcschr(Src, Delim); 1545 if (End == NULL) 1546 End = Src + _tcslen(Src); 1547 APPEND(Src, End - Src); 1548 Src = End; 1549 continue; 1550 } 1551 1552 if (!SubstituteVar(Src, &SrcIncLen, Dest, DestEnd, &DestIncLen, Delim)) 1553 { 1554 return FALSE; 1555 } 1556 else 1557 { 1558 Src += SrcIncLen; 1559 Dest += DestIncLen; 1560 } 1561 } 1562 APPEND1(_T('\0')); 1563 return TRUE; 1564 1565 too_long: 1566 ConOutResPrintf(STRING_ALIAS_ERROR); 1567 nErrorLevel = 9023; 1568 return FALSE; 1569 1570 #undef APPEND 1571 #undef APPEND1 1572 } 1573 1574 /* Search the list of FOR contexts for a variable */ 1575 static BOOL 1576 FindForVar( 1577 IN TCHAR Var, 1578 OUT PCTSTR* VarPtr, 1579 OUT BOOL* IsParam0) 1580 { 1581 PFOR_CONTEXT Ctx; 1582 1583 *VarPtr = NULL; 1584 *IsParam0 = FALSE; 1585 1586 for (Ctx = fc; Ctx != NULL; Ctx = Ctx->prev) 1587 { 1588 if ((UINT)(Var - Ctx->firstvar) < Ctx->varcount) 1589 { 1590 *VarPtr = Ctx->values[Var - Ctx->firstvar]; 1591 return TRUE; 1592 } 1593 } 1594 return FALSE; 1595 } 1596 1597 BOOL 1598 SubstituteForVars( 1599 IN PCTSTR Src, 1600 OUT PTSTR Dest) 1601 { 1602 PTCHAR DestEnd = &Dest[CMDLINE_LENGTH - 1]; 1603 while (*Src) 1604 { 1605 if (Src[0] == _T('%')) 1606 { 1607 BOOL Dummy; 1608 PCTSTR End = &Src[2]; 1609 PCTSTR Value = NULL; 1610 1611 if (Src[1] == _T('~')) 1612 Value = GetEnhancedVar(&End, FindForVar); 1613 1614 if (!Value && Src[1]) 1615 { 1616 if (FindForVar(Src[1], &Value, &Dummy) && !Value) 1617 { 1618 /* The variable is empty, return an empty string */ 1619 Value = _T(""); 1620 } 1621 } 1622 1623 if (Value) 1624 { 1625 if (Dest + _tcslen(Value) > DestEnd) 1626 return FALSE; 1627 Dest = _stpcpy(Dest, Value); 1628 Src = End; 1629 continue; 1630 } 1631 } 1632 /* Not a variable; just copy the character */ 1633 if (Dest >= DestEnd) 1634 return FALSE; 1635 *Dest++ = *Src++; 1636 } 1637 *Dest = _T('\0'); 1638 return TRUE; 1639 } 1640 1641 PTSTR 1642 DoDelayedExpansion( 1643 IN PCTSTR Line) 1644 { 1645 TCHAR Buf1[CMDLINE_LENGTH]; 1646 TCHAR Buf2[CMDLINE_LENGTH]; 1647 PTCHAR Src, Dst; 1648 PTCHAR DestEnd = Buf2 + CMDLINE_LENGTH - 1; 1649 size_t SrcIncLen, DestIncLen; 1650 1651 /* First, substitute FOR variables */ 1652 if (!SubstituteForVars(Line, Buf1)) 1653 return NULL; 1654 1655 if (!bDelayedExpansion || !_tcschr(Buf1, _T('!'))) 1656 return cmd_dup(Buf1); 1657 1658 /* 1659 * Delayed substitutions are not actually completely the same as 1660 * immediate substitutions. In particular, it is possible to escape 1661 * the exclamation point using the escape caret. 1662 */ 1663 1664 /* 1665 * Perform delayed expansion: expand variables around '!', 1666 * and reparse escape carets. 1667 */ 1668 1669 #define APPEND1(Char) \ 1670 do { \ 1671 if (Dst >= DestEnd) \ 1672 goto too_long; \ 1673 *Dst++ = (Char); \ 1674 } while (0) 1675 1676 Src = Buf1; 1677 Dst = Buf2; 1678 while (*Src && (Src < &Buf1[CMDLINE_LENGTH])) 1679 { 1680 if (*Src == _T('^')) 1681 { 1682 ++Src; 1683 if (!*Src || !(Src < &Buf1[CMDLINE_LENGTH])) 1684 break; 1685 1686 APPEND1(*Src++); 1687 } 1688 else if (*Src == _T('!')) 1689 { 1690 if (!SubstituteVar(Src, &SrcIncLen, Dst, DestEnd, &DestIncLen, _T('!'))) 1691 { 1692 return NULL; // Got an error during parsing. 1693 } 1694 else 1695 { 1696 Src += SrcIncLen; 1697 Dst += DestIncLen; 1698 } 1699 } 1700 else 1701 { 1702 APPEND1(*Src++); 1703 } 1704 continue; 1705 } 1706 APPEND1(_T('\0')); 1707 1708 return cmd_dup(Buf2); 1709 1710 too_long: 1711 ConOutResPrintf(STRING_ALIAS_ERROR); 1712 nErrorLevel = 9023; 1713 return NULL; 1714 1715 #undef APPEND1 1716 } 1717 1718 1719 /* 1720 * Do the prompt/input/process loop. 1721 */ 1722 BOOL 1723 ReadLine(TCHAR *commandline, BOOL bMore) 1724 { 1725 TCHAR readline[CMDLINE_LENGTH]; 1726 LPTSTR ip; 1727 1728 /* if no batch input then... */ 1729 if (bc == NULL) 1730 { 1731 if (bMore) 1732 { 1733 ConOutResPrintf(STRING_MORE); 1734 } 1735 else 1736 { 1737 /* JPP 19980807 - if echo off, don't print prompt */ 1738 if (bEcho) 1739 { 1740 if (!bIgnoreEcho) 1741 ConOutChar(_T('\n')); 1742 PrintPrompt(); 1743 } 1744 } 1745 1746 if (!ReadCommand(readline, CMDLINE_LENGTH - 1)) 1747 { 1748 bExit = TRUE; 1749 return FALSE; 1750 } 1751 1752 if (readline[0] == _T('\0')) 1753 ConOutChar(_T('\n')); 1754 1755 if (CheckCtrlBreak(BREAK_INPUT)) 1756 return FALSE; 1757 1758 if (readline[0] == _T('\0')) 1759 return FALSE; 1760 1761 ip = readline; 1762 } 1763 else 1764 { 1765 ip = ReadBatchLine(); 1766 if (!ip) 1767 return FALSE; 1768 } 1769 1770 return SubstituteVars(ip, commandline, _T('%')); 1771 } 1772 1773 static INT 1774 ProcessInput(VOID) 1775 { 1776 INT Ret = 0; 1777 PARSED_COMMAND *Cmd; 1778 1779 while (!bCanExit || !bExit) 1780 { 1781 /* Reset the Ctrl-Break / Ctrl-C state */ 1782 bCtrlBreak = FALSE; 1783 1784 Cmd = ParseCommand(NULL); 1785 if (!Cmd) 1786 continue; 1787 1788 Ret = ExecuteCommand(Cmd); 1789 FreeCommand(Cmd); 1790 } 1791 1792 return Ret; 1793 } 1794 1795 1796 /* 1797 * Control-break handler. 1798 */ 1799 static BOOL 1800 WINAPI 1801 BreakHandler(IN DWORD dwCtrlType) 1802 { 1803 DWORD dwWritten; 1804 INPUT_RECORD rec; 1805 1806 if ((dwCtrlType != CTRL_C_EVENT) && 1807 (dwCtrlType != CTRL_BREAK_EVENT)) 1808 { 1809 return FALSE; 1810 } 1811 1812 if (!TryEnterCriticalSection(&ChildProcessRunningLock)) 1813 { 1814 /* Child process is running and will have received the control event */ 1815 return TRUE; 1816 } 1817 else 1818 { 1819 LeaveCriticalSection(&ChildProcessRunningLock); 1820 } 1821 1822 bCtrlBreak = TRUE; 1823 1824 rec.EventType = KEY_EVENT; 1825 rec.Event.KeyEvent.bKeyDown = TRUE; 1826 rec.Event.KeyEvent.wRepeatCount = 1; 1827 rec.Event.KeyEvent.wVirtualKeyCode = _T('C'); 1828 rec.Event.KeyEvent.wVirtualScanCode = _T('C') - 35; 1829 rec.Event.KeyEvent.uChar.AsciiChar = _T('C'); 1830 rec.Event.KeyEvent.uChar.UnicodeChar = _T('C'); 1831 rec.Event.KeyEvent.dwControlKeyState = RIGHT_CTRL_PRESSED; 1832 1833 WriteConsoleInput(ConStreamGetOSHandle(StdIn), 1834 &rec, 1835 1, 1836 &dwWritten); 1837 1838 /* FIXME: Handle batch files */ 1839 1840 // ConOutPrintf(_T("^C")); 1841 1842 return TRUE; 1843 } 1844 1845 1846 VOID AddBreakHandler(VOID) 1847 { 1848 SetConsoleCtrlHandler(BreakHandler, TRUE); 1849 } 1850 1851 1852 VOID RemoveBreakHandler(VOID) 1853 { 1854 SetConsoleCtrlHandler(BreakHandler, FALSE); 1855 } 1856 1857 1858 /* 1859 * Show commands and options that are available. 1860 */ 1861 #if 0 1862 static VOID 1863 ShowCommands(VOID) 1864 { 1865 /* print command list */ 1866 ConOutResPuts(STRING_CMD_HELP1); 1867 PrintCommandList(); 1868 1869 /* print feature list */ 1870 ConOutResPuts(STRING_CMD_HELP2); 1871 1872 #ifdef FEATURE_ALIASES 1873 ConOutResPuts(STRING_CMD_HELP3); 1874 #endif 1875 #ifdef FEATURE_HISTORY 1876 ConOutResPuts(STRING_CMD_HELP4); 1877 #endif 1878 #ifdef FEATURE_UNIX_FILENAME_COMPLETION 1879 ConOutResPuts(STRING_CMD_HELP5); 1880 #endif 1881 #ifdef FEATURE_DIRECTORY_STACK 1882 ConOutResPuts(STRING_CMD_HELP6); 1883 #endif 1884 #ifdef FEATURE_REDIRECTION 1885 ConOutResPuts(STRING_CMD_HELP7); 1886 #endif 1887 ConOutChar(_T('\n')); 1888 } 1889 #endif 1890 1891 1892 static VOID 1893 LoadRegistrySettings(HKEY hKeyRoot) 1894 { 1895 LONG lRet; 1896 HKEY hKey; 1897 DWORD dwType, len; 1898 /* 1899 * Buffer big enough to hold the string L"4294967295", 1900 * corresponding to the literal 0xFFFFFFFF (MAXULONG) in decimal. 1901 */ 1902 DWORD Buffer[6]; 1903 1904 lRet = RegOpenKeyEx(hKeyRoot, 1905 _T("Software\\Microsoft\\Command Processor"), 1906 0, 1907 KEY_QUERY_VALUE, 1908 &hKey); 1909 if (lRet != ERROR_SUCCESS) 1910 return; 1911 1912 #ifdef INCLUDE_CMD_COLOR 1913 len = sizeof(Buffer); 1914 lRet = RegQueryValueEx(hKey, 1915 _T("DefaultColor"), 1916 NULL, 1917 &dwType, 1918 (LPBYTE)&Buffer, 1919 &len); 1920 if (lRet == ERROR_SUCCESS) 1921 { 1922 /* Overwrite the default attributes */ 1923 if (dwType == REG_DWORD) 1924 wDefColor = (WORD)*(PDWORD)Buffer; 1925 else if (dwType == REG_SZ) 1926 wDefColor = (WORD)_tcstol((PTSTR)Buffer, NULL, 0); 1927 } 1928 // else, use the default attributes retrieved before. 1929 #endif 1930 1931 #if 0 1932 len = sizeof(Buffer); 1933 lRet = RegQueryValueEx(hKey, 1934 _T("DisableUNCCheck"), 1935 NULL, 1936 &dwType, 1937 (LPBYTE)&Buffer, 1938 &len); 1939 if (lRet == ERROR_SUCCESS) 1940 { 1941 /* Overwrite the default setting */ 1942 if (dwType == REG_DWORD) 1943 bDisableUNCCheck = !!*(PDWORD)Buffer; 1944 else if (dwType == REG_SZ) 1945 bDisableUNCCheck = (_ttol((PTSTR)Buffer) == 1); 1946 } 1947 // else, use the default setting set globally. 1948 #endif 1949 1950 len = sizeof(Buffer); 1951 lRet = RegQueryValueEx(hKey, 1952 _T("DelayedExpansion"), 1953 NULL, 1954 &dwType, 1955 (LPBYTE)&Buffer, 1956 &len); 1957 if (lRet == ERROR_SUCCESS) 1958 { 1959 /* Overwrite the default setting */ 1960 if (dwType == REG_DWORD) 1961 bDelayedExpansion = !!*(PDWORD)Buffer; 1962 else if (dwType == REG_SZ) 1963 bDelayedExpansion = (_ttol((PTSTR)Buffer) == 1); 1964 } 1965 // else, use the default setting set globally. 1966 1967 len = sizeof(Buffer); 1968 lRet = RegQueryValueEx(hKey, 1969 _T("EnableExtensions"), 1970 NULL, 1971 &dwType, 1972 (LPBYTE)&Buffer, 1973 &len); 1974 if (lRet == ERROR_SUCCESS) 1975 { 1976 /* Overwrite the default setting */ 1977 if (dwType == REG_DWORD) 1978 bEnableExtensions = !!*(PDWORD)Buffer; 1979 else if (dwType == REG_SZ) 1980 bEnableExtensions = (_ttol((PTSTR)Buffer) == 1); 1981 } 1982 // else, use the default setting set globally. 1983 1984 len = sizeof(Buffer); 1985 lRet = RegQueryValueEx(hKey, 1986 _T("CompletionChar"), 1987 NULL, 1988 &dwType, 1989 (LPBYTE)&Buffer, 1990 &len); 1991 if (lRet == ERROR_SUCCESS) 1992 { 1993 /* Overwrite the default setting */ 1994 if (dwType == REG_DWORD) 1995 AutoCompletionChar = (TCHAR)*(PDWORD)Buffer; 1996 else if (dwType == REG_SZ) 1997 AutoCompletionChar = (TCHAR)_tcstol((PTSTR)Buffer, NULL, 0); 1998 } 1999 // else, use the default setting set globally. 2000 2001 /* Validity check */ 2002 if (IS_COMPLETION_DISABLED(AutoCompletionChar)) 2003 { 2004 /* Disable autocompletion */ 2005 AutoCompletionChar = 0x20; 2006 } 2007 2008 len = sizeof(Buffer); 2009 lRet = RegQueryValueEx(hKey, 2010 _T("PathCompletionChar"), 2011 NULL, 2012 &dwType, 2013 (LPBYTE)&Buffer, 2014 &len); 2015 if (lRet == ERROR_SUCCESS) 2016 { 2017 /* Overwrite the default setting */ 2018 if (dwType == REG_DWORD) 2019 PathCompletionChar = (TCHAR)*(PDWORD)Buffer; 2020 else if (dwType == REG_SZ) 2021 PathCompletionChar = (TCHAR)_tcstol((PTSTR)Buffer, NULL, 0); 2022 } 2023 // else, use the default setting set globally. 2024 2025 /* Validity check */ 2026 if (IS_COMPLETION_DISABLED(PathCompletionChar)) 2027 { 2028 /* Disable autocompletion */ 2029 PathCompletionChar = 0x20; 2030 } 2031 2032 /* Adjust completion chars */ 2033 if (PathCompletionChar >= 0x20 && AutoCompletionChar < 0x20) 2034 PathCompletionChar = AutoCompletionChar; 2035 else if (AutoCompletionChar >= 0x20 && PathCompletionChar < 0x20) 2036 AutoCompletionChar = PathCompletionChar; 2037 2038 RegCloseKey(hKey); 2039 } 2040 2041 static VOID 2042 ExecuteAutoRunFile(HKEY hKeyRoot) 2043 { 2044 LONG lRet; 2045 HKEY hKey; 2046 DWORD dwType, len; 2047 TCHAR AutoRun[2048]; 2048 2049 lRet = RegOpenKeyEx(hKeyRoot, 2050 _T("Software\\Microsoft\\Command Processor"), 2051 0, 2052 KEY_QUERY_VALUE, 2053 &hKey); 2054 if (lRet != ERROR_SUCCESS) 2055 return; 2056 2057 len = sizeof(AutoRun); 2058 lRet = RegQueryValueEx(hKey, 2059 _T("AutoRun"), 2060 NULL, 2061 &dwType, 2062 (LPBYTE)&AutoRun, 2063 &len); 2064 if ((lRet == ERROR_SUCCESS) && (dwType == REG_EXPAND_SZ || dwType == REG_SZ)) 2065 { 2066 if (*AutoRun) 2067 ParseCommandLine(AutoRun); 2068 } 2069 2070 RegCloseKey(hKey); 2071 } 2072 2073 /* Get the command that comes after a /C or /K switch */ 2074 static VOID 2075 GetCmdLineCommand( 2076 OUT LPTSTR commandline, 2077 IN LPCTSTR ptr, 2078 IN BOOL AlwaysStrip) 2079 { 2080 TCHAR* LastQuote; 2081 2082 while (_istspace(*ptr)) 2083 ++ptr; 2084 2085 /* Remove leading quote, find final quote */ 2086 if (*ptr == _T('"') && 2087 (LastQuote = _tcsrchr(++ptr, _T('"'))) != NULL) 2088 { 2089 const TCHAR* Space; 2090 /* Under certain circumstances, all quotes are preserved. 2091 * CMD /? documents these conditions as follows: 2092 * 1. No /S switch 2093 * 2. Exactly two quotes 2094 * 3. No "special characters" between the quotes 2095 * (CMD /? says &<>()@^| but parentheses did not 2096 * trigger this rule when I tested them.) 2097 * 4. Whitespace exists between the quotes 2098 * 5. Enclosed string is an executable filename 2099 */ 2100 *LastQuote = _T('\0'); 2101 for (Space = ptr + 1; Space < LastQuote; ++Space) 2102 { 2103 if (_istspace(*Space)) /* Rule 4 */ 2104 { 2105 if (!AlwaysStrip && /* Rule 1 */ 2106 !_tcspbrk(ptr, _T("\"&<>@^|")) && /* Rules 2, 3 */ 2107 SearchForExecutable(ptr, commandline)) /* Rule 5 */ 2108 { 2109 /* All conditions met: preserve both the quotes */ 2110 *LastQuote = _T('"'); 2111 _tcscpy(commandline, ptr - 1); 2112 return; 2113 } 2114 break; 2115 } 2116 } 2117 2118 /* The conditions were not met: remove both the 2119 * leading quote and the last quote */ 2120 _tcscpy(commandline, ptr); 2121 _tcscpy(&commandline[LastQuote - ptr], LastQuote + 1); 2122 return; 2123 } 2124 2125 /* No quotes; just copy */ 2126 _tcscpy(commandline, ptr); 2127 } 2128 2129 2130 /* 2131 * Set up global initializations and process parameters. 2132 * Return a pointer to the command line if present. 2133 */ 2134 static LPCTSTR 2135 Initialize(VOID) 2136 { 2137 HMODULE NtDllModule; 2138 HANDLE hIn, hOut; 2139 LPTSTR ptr, cmdLine; 2140 TCHAR option = 0; 2141 BOOL AutoRun = TRUE; 2142 TCHAR ModuleName[MAX_PATH + 1]; 2143 2144 /* Get version information */ 2145 InitOSVersion(); 2146 2147 /* Some people like to run ReactOS cmd.exe on Win98, it helps in the 2148 * build process. So don't link implicitly against ntdll.dll, load it 2149 * dynamically instead */ 2150 NtDllModule = GetModuleHandle(TEXT("ntdll.dll")); 2151 if (NtDllModule != NULL) 2152 { 2153 NtQueryInformationProcessPtr = (NtQueryInformationProcessProc)GetProcAddress(NtDllModule, "NtQueryInformationProcess"); 2154 NtReadVirtualMemoryPtr = (NtReadVirtualMemoryProc)GetProcAddress(NtDllModule, "NtReadVirtualMemory"); 2155 } 2156 2157 /* Load the registry settings */ 2158 LoadRegistrySettings(HKEY_LOCAL_MACHINE); 2159 LoadRegistrySettings(HKEY_CURRENT_USER); 2160 2161 /* Initialize our locale */ 2162 InitLocale(); 2163 2164 /* Initialize prompt support */ 2165 InitPrompt(); 2166 2167 #ifdef FEATURE_DIRECTORY_STACK 2168 /* Initialize directory stack */ 2169 InitDirectoryStack(); 2170 #endif 2171 2172 #ifdef FEATURE_HISTORY 2173 /* Initialize history */ 2174 InitHistory(); 2175 #endif 2176 2177 /* Set COMSPEC environment variable */ 2178 if (GetModuleFileName(NULL, ModuleName, ARRAYSIZE(ModuleName)) != 0) 2179 { 2180 ModuleName[MAX_PATH] = _T('\0'); 2181 SetEnvironmentVariable (_T("COMSPEC"), ModuleName); 2182 } 2183 2184 /* Add ctrl break handler */ 2185 AddBreakHandler(); 2186 2187 /* Set the default console mode */ 2188 hOut = ConStreamGetOSHandle(StdOut); 2189 hIn = ConStreamGetOSHandle(StdIn); 2190 SetConsoleMode(hOut, 0); // Reinitialize the console output mode 2191 SetConsoleMode(hOut, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT); 2192 SetConsoleMode(hIn , ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); 2193 2194 cmdLine = GetCommandLine(); 2195 TRACE ("[command args: %s]\n", debugstr_aw(cmdLine)); 2196 2197 for (ptr = cmdLine; *ptr; ++ptr) 2198 { 2199 if (*ptr == _T('/')) 2200 { 2201 option = _totupper(ptr[1]); 2202 if (option == _T('?')) 2203 { 2204 ConOutResPaging(TRUE, STRING_CMD_HELP8); 2205 nErrorLevel = 1; 2206 bExit = TRUE; 2207 return NULL; 2208 } 2209 else if (option == _T('P')) 2210 { 2211 if (!IsExistingFile(_T("\\autoexec.bat"))) 2212 { 2213 #ifdef INCLUDE_CMD_DATE 2214 cmd_date(_T("")); 2215 #endif 2216 #ifdef INCLUDE_CMD_TIME 2217 cmd_time(_T("")); 2218 #endif 2219 } 2220 else 2221 { 2222 ParseCommandLine(_T("\\autoexec.bat")); 2223 } 2224 bCanExit = FALSE; 2225 } 2226 else if (option == _T('A')) 2227 { 2228 OutputStreamMode = AnsiText; 2229 } 2230 else if (option == _T('C') || option == _T('K') || option == _T('R')) 2231 { 2232 /* Remainder of command line is a command to be run */ 2233 fSingleCommand = ((option == _T('K')) << 1) | 1; 2234 break; 2235 } 2236 else if (option == _T('D')) 2237 { 2238 AutoRun = FALSE; 2239 } 2240 else if (option == _T('Q')) 2241 { 2242 bDisableBatchEcho = TRUE; 2243 } 2244 else if (option == _T('S')) 2245 { 2246 bAlwaysStrip = TRUE; 2247 } 2248 #ifdef INCLUDE_CMD_COLOR 2249 else if (!_tcsnicmp(ptr, _T("/T:"), 3)) 2250 { 2251 /* Process /T (color) argument; overwrite any previous settings */ 2252 wDefColor = (WORD)_tcstoul(&ptr[3], &ptr, 16); 2253 } 2254 #endif 2255 else if (option == _T('U')) 2256 { 2257 OutputStreamMode = UTF16Text; 2258 } 2259 else if (option == _T('V')) 2260 { 2261 // FIXME: Check validity of the parameter given to V ! 2262 bDelayedExpansion = _tcsnicmp(&ptr[2], _T(":OFF"), 4); 2263 } 2264 else if (option == _T('E')) 2265 { 2266 // FIXME: Check validity of the parameter given to E ! 2267 bEnableExtensions = _tcsnicmp(&ptr[2], _T(":OFF"), 4); 2268 } 2269 else if (option == _T('X')) 2270 { 2271 /* '/X' is identical to '/E:ON' */ 2272 bEnableExtensions = TRUE; 2273 } 2274 else if (option == _T('Y')) 2275 { 2276 /* '/Y' is identical to '/E:OFF' */ 2277 bEnableExtensions = FALSE; 2278 } 2279 } 2280 } 2281 2282 #ifdef INCLUDE_CMD_COLOR 2283 if (wDefColor == 0) 2284 { 2285 /* 2286 * If we still do not have the console colour attribute set, 2287 * retrieve the default one. 2288 */ 2289 ConGetDefaultAttributes(&wDefColor); 2290 } 2291 2292 if (wDefColor != 0) 2293 ConSetScreenColor(ConStreamGetOSHandle(StdOut), wDefColor, TRUE); 2294 #endif 2295 2296 /* Reset the output Standard Streams translation modes and code page caches */ 2297 // ConStreamSetMode(StdIn , OutputStreamMode, InputCodePage ); 2298 ConStreamSetMode(StdOut, OutputStreamMode, OutputCodePage); 2299 ConStreamSetMode(StdErr, OutputStreamMode, OutputCodePage); 2300 2301 if (!*ptr) 2302 { 2303 /* If neither /C or /K was given, display a simple version string */ 2304 ConOutChar(_T('\n')); 2305 ConOutResPrintf(STRING_REACTOS_VERSION, 2306 _T(KERNEL_VERSION_STR), 2307 _T(KERNEL_VERSION_BUILD_STR)); 2308 ConOutPuts(_T("(C) Copyright 1998-") _T(COPYRIGHT_YEAR) _T(" ReactOS Team.\n")); 2309 } 2310 2311 if (AutoRun) 2312 { 2313 ExecuteAutoRunFile(HKEY_LOCAL_MACHINE); 2314 ExecuteAutoRunFile(HKEY_CURRENT_USER); 2315 } 2316 2317 /* Returns the rest of the command line */ 2318 return ptr; 2319 } 2320 2321 2322 static VOID Cleanup(VOID) 2323 { 2324 /* Run cmdexit.bat */ 2325 if (IsExistingFile(_T("cmdexit.bat"))) 2326 { 2327 ConErrResPuts(STRING_CMD_ERROR5); 2328 ParseCommandLine(_T("cmdexit.bat")); 2329 } 2330 else if (IsExistingFile(_T("\\cmdexit.bat"))) 2331 { 2332 ConErrResPuts(STRING_CMD_ERROR5); 2333 ParseCommandLine(_T("\\cmdexit.bat")); 2334 } 2335 2336 /* Remove ctrl break handler */ 2337 RemoveBreakHandler(); 2338 2339 /* Restore the default console mode */ 2340 SetConsoleMode(ConStreamGetOSHandle(StdIn), 2341 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); 2342 SetConsoleMode(ConStreamGetOSHandle(StdOut), 2343 ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT); 2344 2345 2346 #ifdef _DEBUG_MEM 2347 #ifdef FEATURE_DIRECTORY_STACK 2348 /* Destroy directory stack */ 2349 DestroyDirectoryStack(); 2350 #endif 2351 2352 #ifdef FEATURE_HISTORY 2353 CleanHistory(); 2354 #endif 2355 2356 /* Free GetEnvVar's buffer */ 2357 GetEnvVar(NULL); 2358 #endif /* _DEBUG_MEM */ 2359 2360 DeleteCriticalSection(&ChildProcessRunningLock); 2361 } 2362 2363 /* 2364 * main function 2365 */ 2366 int _tmain(int argc, const TCHAR *argv[]) 2367 { 2368 INT nExitCode; 2369 LPCTSTR pCmdLine; 2370 TCHAR startPath[MAX_PATH]; 2371 2372 InitializeCriticalSection(&ChildProcessRunningLock); 2373 lpOriginalEnvironment = DuplicateEnvironment(); 2374 2375 GetCurrentDirectory(ARRAYSIZE(startPath), startPath); 2376 _tchdir(startPath); 2377 2378 SetFileApisToOEM(); 2379 InputCodePage = GetConsoleCP(); 2380 OutputCodePage = GetConsoleOutputCP(); 2381 2382 /* Initialize the Console Standard Streams */ 2383 ConStreamInit(StdIn , GetStdHandle(STD_INPUT_HANDLE) , /*OutputStreamMode*/ AnsiText, InputCodePage); 2384 ConStreamInit(StdOut, GetStdHandle(STD_OUTPUT_HANDLE), OutputStreamMode, OutputCodePage); 2385 ConStreamInit(StdErr, GetStdHandle(STD_ERROR_HANDLE) , OutputStreamMode, OutputCodePage); 2386 /* Reset the current thread UI language */ 2387 if (IsConsoleHandle(ConStreamGetOSHandle(StdOut)) || 2388 IsConsoleHandle(ConStreamGetOSHandle(StdErr))) 2389 { 2390 ConSetThreadUILanguage(0); 2391 } 2392 2393 CMD_ModuleHandle = GetModuleHandle(NULL); 2394 2395 /* 2396 * Perform general initialization, parse switches on command-line. 2397 * Initialize the exit code with the errorlevel as Initialize() can set it. 2398 */ 2399 pCmdLine = Initialize(); 2400 nExitCode = nErrorLevel; 2401 2402 if (pCmdLine && *pCmdLine) 2403 { 2404 TCHAR commandline[CMDLINE_LENGTH]; 2405 2406 /* Do the /C or /K command */ 2407 GetCmdLineCommand(commandline, &pCmdLine[2], bAlwaysStrip); 2408 nExitCode = ParseCommandLine(commandline); 2409 if (fSingleCommand == 1) 2410 { 2411 // nErrorLevel = nExitCode; 2412 bExit = TRUE; 2413 } 2414 fSingleCommand = 0; 2415 } 2416 if (!bExit) 2417 { 2418 /* Call prompt routine */ 2419 nExitCode = ProcessInput(); 2420 } 2421 2422 /* Do the cleanup */ 2423 Cleanup(); 2424 cmd_free(lpOriginalEnvironment); 2425 2426 cmd_exit(nExitCode); 2427 return nExitCode; 2428 } 2429 2430 /* EOF */ 2431