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