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