Merge branch 'ntfs_rebase'
[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 INT
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 nErrorLevel;
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 return nErrorLevel;
726 }
727
728 INT
729 ExecuteCommand(PARSED_COMMAND *Cmd)
730 {
731 PARSED_COMMAND *Sub;
732 LPTSTR First, Rest;
733 INT Ret = 0;
734
735 if (!PerformRedirection(Cmd->Redirections))
736 return 1;
737
738 switch (Cmd->Type)
739 {
740 case C_COMMAND:
741 Ret = 1;
742 First = DoDelayedExpansion(Cmd->Command.First);
743 if (First)
744 {
745 Rest = DoDelayedExpansion(Cmd->Command.Rest);
746 if (Rest)
747 {
748 Ret = DoCommand(First, Rest, Cmd);
749 cmd_free(Rest);
750 }
751 cmd_free(First);
752 }
753 break;
754 case C_QUIET:
755 case C_BLOCK:
756 case C_MULTI:
757 for (Sub = Cmd->Subcommands; Sub; Sub = Sub->Next)
758 Ret = ExecuteCommand(Sub);
759 break;
760 case C_IFFAILURE:
761 Sub = Cmd->Subcommands;
762 Ret = ExecuteCommand(Sub);
763 if (Ret != 0)
764 {
765 nErrorLevel = Ret;
766 Ret = ExecuteCommand(Sub->Next);
767 }
768 break;
769 case C_IFSUCCESS:
770 Sub = Cmd->Subcommands;
771 Ret = ExecuteCommand(Sub);
772 if (Ret == 0)
773 Ret = ExecuteCommand(Sub->Next);
774 break;
775 case C_PIPE:
776 Ret = ExecutePipeline(Cmd);
777 break;
778 case C_IF:
779 Ret = ExecuteIf(Cmd);
780 break;
781 case C_FOR:
782 Ret = ExecuteFor(Cmd);
783 break;
784 }
785
786 UndoRedirection(Cmd->Redirections, NULL);
787 return Ret;
788 }
789
790 LPTSTR
791 GetEnvVar(LPCTSTR varName)
792 {
793 static LPTSTR ret = NULL;
794 UINT size;
795
796 cmd_free(ret);
797 ret = NULL;
798 size = GetEnvironmentVariable(varName, NULL, 0);
799 if (size > 0)
800 {
801 ret = cmd_alloc(size * sizeof(TCHAR));
802 if (ret != NULL)
803 GetEnvironmentVariable(varName, ret, size + 1);
804 }
805 return ret;
806 }
807
808 LPCTSTR
809 GetEnvVarOrSpecial(LPCTSTR varName)
810 {
811 static TCHAR ret[MAX_PATH];
812
813 LPTSTR var = GetEnvVar(varName);
814 if (var)
815 return var;
816
817 /* env var doesn't exist, look for a "special" one */
818 /* %CD% */
819 if (_tcsicmp(varName,_T("cd")) ==0)
820 {
821 GetCurrentDirectory(MAX_PATH, ret);
822 return ret;
823 }
824 /* %TIME% */
825 else if (_tcsicmp(varName,_T("time")) ==0)
826 {
827 return GetTimeString();
828 }
829 /* %DATE% */
830 else if (_tcsicmp(varName,_T("date")) ==0)
831 {
832 return GetDateString();
833 }
834
835 /* %RANDOM% */
836 else if (_tcsicmp(varName,_T("random")) ==0)
837 {
838 /* Get random number */
839 _itot(rand(),ret,10);
840 return ret;
841 }
842
843 /* %CMDCMDLINE% */
844 else if (_tcsicmp(varName,_T("cmdcmdline")) ==0)
845 {
846 return GetCommandLine();
847 }
848
849 /* %CMDEXTVERSION% */
850 else if (_tcsicmp(varName,_T("cmdextversion")) ==0)
851 {
852 /* Set version number to 2 */
853 _itot(2,ret,10);
854 return ret;
855 }
856
857 /* %ERRORLEVEL% */
858 else if (_tcsicmp(varName,_T("errorlevel")) ==0)
859 {
860 _itot(nErrorLevel,ret,10);
861 return ret;
862 }
863
864 return NULL;
865 }
866
867 /* Handle the %~var syntax */
868 static LPTSTR
869 GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, BOOL *))
870 {
871 static const TCHAR ModifierTable[] = _T("dpnxfsatz");
872 enum {
873 M_DRIVE = 1, /* D: drive letter */
874 M_PATH = 2, /* P: path */
875 M_NAME = 4, /* N: filename */
876 M_EXT = 8, /* X: extension */
877 M_FULL = 16, /* F: full path (drive+path+name+ext) */
878 M_SHORT = 32, /* S: full path (drive+path+name+ext), use short names */
879 M_ATTR = 64, /* A: attributes */
880 M_TIME = 128, /* T: modification time */
881 M_SIZE = 256, /* Z: file size */
882 } Modifiers = 0;
883
884 TCHAR *Format, *FormatEnd;
885 TCHAR *PathVarName = NULL;
886 LPTSTR Variable;
887 TCHAR *VarEnd;
888 BOOL VariableIsParam0;
889 TCHAR FullPath[MAX_PATH];
890 TCHAR FixedPath[MAX_PATH], *Filename, *Extension;
891 HANDLE hFind;
892 WIN32_FIND_DATA w32fd;
893 TCHAR *In, *Out;
894
895 static TCHAR Result[CMDLINE_LENGTH];
896
897 /* There is ambiguity between modifier characters and FOR variables;
898 * the rule that cmd uses is to pick the longest possible match.
899 * For example, if there is a %n variable, then out of %~anxnd,
900 * %~anxn will be substituted rather than just %~an. */
901
902 /* First, go through as many modifier characters as possible */
903 FormatEnd = Format = *pFormat;
904 while (*FormatEnd && _tcschr(ModifierTable, _totlower(*FormatEnd)))
905 FormatEnd++;
906
907 if (*FormatEnd == _T('$'))
908 {
909 /* $PATH: syntax */
910 PathVarName = FormatEnd + 1;
911 FormatEnd = _tcschr(PathVarName, _T(':'));
912 if (!FormatEnd)
913 return NULL;
914
915 /* Must be immediately followed by the variable */
916 Variable = GetVar(*++FormatEnd, &VariableIsParam0);
917 if (!Variable)
918 return NULL;
919 }
920 else
921 {
922 /* Backtrack if necessary to get a variable name match */
923 while (!(Variable = GetVar(*FormatEnd, &VariableIsParam0)))
924 {
925 if (FormatEnd == Format)
926 return NULL;
927 FormatEnd--;
928 }
929 }
930
931 for (; Format < FormatEnd && *Format != _T('$'); Format++)
932 Modifiers |= 1 << (_tcschr(ModifierTable, _totlower(*Format)) - ModifierTable);
933
934 *pFormat = FormatEnd + 1;
935
936 /* Exclude the leading and trailing quotes */
937 VarEnd = &Variable[_tcslen(Variable)];
938 if (*Variable == _T('"'))
939 {
940 Variable++;
941 if (VarEnd > Variable && VarEnd[-1] == _T('"'))
942 VarEnd--;
943 }
944
945 if ((char *)VarEnd - (char *)Variable >= sizeof Result)
946 return _T("");
947 memcpy(Result, Variable, (char *)VarEnd - (char *)Variable);
948 Result[VarEnd - Variable] = _T('\0');
949
950 if (PathVarName)
951 {
952 /* $PATH: syntax - search the directories listed in the
953 * specified environment variable for the file */
954 LPTSTR PathVar;
955 FormatEnd[-1] = _T('\0');
956 PathVar = GetEnvVar(PathVarName);
957 FormatEnd[-1] = _T(':');
958 if (!PathVar ||
959 !SearchPath(PathVar, Result, NULL, MAX_PATH, FullPath, NULL))
960 {
961 return _T("");
962 }
963 }
964 else if (Modifiers == 0)
965 {
966 /* For plain %~var with no modifiers, just return the variable without quotes */
967 return Result;
968 }
969 else if (VariableIsParam0)
970 {
971 /* Special case: If the variable is %0 and modifier characters are present,
972 * use the batch file's path (which includes the .bat/.cmd extension)
973 * rather than the actual %0 variable (which might not). */
974 _tcscpy(FullPath, bc->BatchFilePath);
975 }
976 else
977 {
978 /* Convert the variable, now without quotes, to a full path */
979 if (!GetFullPathName(Result, MAX_PATH, FullPath, NULL))
980 return _T("");
981 }
982
983 /* Next step is to change the path to fix letter case (e.g.
984 * C:\ReAcToS -> C:\ReactOS) and, if requested with the S modifier,
985 * replace long filenames with short. */
986
987 In = FullPath;
988 Out = FixedPath;
989
990 /* Copy drive letter */
991 *Out++ = *In++;
992 *Out++ = *In++;
993 *Out++ = *In++;
994 /* Loop over each \-separated component in the path */
995 do {
996 TCHAR *Next = _tcschr(In, _T('\\'));
997 if (Next)
998 *Next++ = _T('\0');
999 /* Use FindFirstFile to get the correct name */
1000 if (Out + _tcslen(In) + 1 >= &FixedPath[MAX_PATH])
1001 return _T("");
1002 _tcscpy(Out, In);
1003 hFind = FindFirstFile(FixedPath, &w32fd);
1004 /* If it doesn't exist, just leave the name as it was given */
1005 if (hFind != INVALID_HANDLE_VALUE)
1006 {
1007 LPTSTR FixedComponent = w32fd.cFileName;
1008 if (*w32fd.cAlternateFileName &&
1009 ((Modifiers & M_SHORT) || !_tcsicmp(In, w32fd.cAlternateFileName)))
1010 {
1011 FixedComponent = w32fd.cAlternateFileName;
1012 }
1013 FindClose(hFind);
1014
1015 if (Out + _tcslen(FixedComponent) + 1 >= &FixedPath[MAX_PATH])
1016 return _T("");
1017 _tcscpy(Out, FixedComponent);
1018 }
1019 Filename = Out;
1020 Out += _tcslen(Out);
1021 *Out++ = _T('\\');
1022
1023 In = Next;
1024 } while (In != NULL);
1025 Out[-1] = _T('\0');
1026
1027 /* Build the result string. Start with attributes, modification time, and
1028 * file size. If the file didn't exist, these fields will all be empty. */
1029 Out = Result;
1030 if (hFind != INVALID_HANDLE_VALUE)
1031 {
1032 if (Modifiers & M_ATTR)
1033 {
1034 static const struct {
1035 TCHAR Character;
1036 WORD Value;
1037 } *Attrib, Table[] = {
1038 { _T('d'), FILE_ATTRIBUTE_DIRECTORY },
1039 { _T('r'), FILE_ATTRIBUTE_READONLY },
1040 { _T('a'), FILE_ATTRIBUTE_ARCHIVE },
1041 { _T('h'), FILE_ATTRIBUTE_HIDDEN },
1042 { _T('s'), FILE_ATTRIBUTE_SYSTEM },
1043 { _T('c'), FILE_ATTRIBUTE_COMPRESSED },
1044 { _T('o'), FILE_ATTRIBUTE_OFFLINE },
1045 { _T('t'), FILE_ATTRIBUTE_TEMPORARY },
1046 { _T('l'), FILE_ATTRIBUTE_REPARSE_POINT },
1047 };
1048 for (Attrib = Table; Attrib != &Table[9]; Attrib++)
1049 {
1050 *Out++ = w32fd.dwFileAttributes & Attrib->Value
1051 ? Attrib->Character
1052 : _T('-');
1053 }
1054 *Out++ = _T(' ');
1055 }
1056 if (Modifiers & M_TIME)
1057 {
1058 FILETIME ft;
1059 SYSTEMTIME st;
1060 FileTimeToLocalFileTime(&w32fd.ftLastWriteTime, &ft);
1061 FileTimeToSystemTime(&ft, &st);
1062
1063 Out += FormatDate(Out, &st, TRUE);
1064 *Out++ = _T(' ');
1065 Out += FormatTime(Out, &st);
1066 *Out++ = _T(' ');
1067 }
1068 if (Modifiers & M_SIZE)
1069 {
1070 ULARGE_INTEGER Size;
1071 Size.LowPart = w32fd.nFileSizeLow;
1072 Size.HighPart = w32fd.nFileSizeHigh;
1073 Out += _stprintf(Out, _T("%I64u "), Size.QuadPart);
1074 }
1075 }
1076
1077 /* When using the path-searching syntax or the S modifier,
1078 * at least part of the file path is always included.
1079 * If none of the DPNX modifiers are present, include the full path */
1080 if (PathVarName || (Modifiers & M_SHORT))
1081 if ((Modifiers & (M_DRIVE | M_PATH | M_NAME | M_EXT)) == 0)
1082 Modifiers |= M_FULL;
1083
1084 /* Now add the requested parts of the name.
1085 * With the F modifier, add all parts to form the full path. */
1086 Extension = _tcsrchr(Filename, _T('.'));
1087 if (Modifiers & (M_DRIVE | M_FULL))
1088 {
1089 *Out++ = FixedPath[0];
1090 *Out++ = FixedPath[1];
1091 }
1092 if (Modifiers & (M_PATH | M_FULL))
1093 {
1094 memcpy(Out, &FixedPath[2], (char *)Filename - (char *)&FixedPath[2]);
1095 Out += Filename - &FixedPath[2];
1096 }
1097 if (Modifiers & (M_NAME | M_FULL))
1098 {
1099 while (*Filename && Filename != Extension)
1100 *Out++ = *Filename++;
1101 }
1102 if (Modifiers & (M_EXT | M_FULL))
1103 {
1104 if (Extension)
1105 Out = _stpcpy(Out, Extension);
1106 }
1107
1108 /* Trim trailing space which otherwise would appear as a
1109 * result of using the A/T/Z modifiers but no others. */
1110 while (Out != &Result[0] && Out[-1] == _T(' '))
1111 Out--;
1112 *Out = _T('\0');
1113
1114 return Result;
1115 }
1116
1117 LPCTSTR
1118 GetBatchVar(TCHAR *varName, UINT *varNameLen)
1119 {
1120 LPCTSTR ret;
1121 TCHAR *varNameEnd;
1122 BOOL dummy;
1123
1124 *varNameLen = 1;
1125
1126 switch ( *varName )
1127 {
1128 case _T('~'):
1129 varNameEnd = varName + 1;
1130 ret = GetEnhancedVar(&varNameEnd, FindArg);
1131 if (!ret)
1132 {
1133 error_syntax(varName);
1134 return NULL;
1135 }
1136 *varNameLen = varNameEnd - varName;
1137 return ret;
1138 case _T('0'):
1139 case _T('1'):
1140 case _T('2'):
1141 case _T('3'):
1142 case _T('4'):
1143 case _T('5'):
1144 case _T('6'):
1145 case _T('7'):
1146 case _T('8'):
1147 case _T('9'):
1148 return FindArg(*varName, &dummy);
1149
1150 case _T('*'):
1151 //
1152 // Copy over the raw params(not including the batch file name
1153 //
1154 return bc->raw_params;
1155
1156 case _T('%'):
1157 return _T("%");
1158 }
1159 return NULL;
1160 }
1161
1162 BOOL
1163 SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim)
1164 {
1165 #define APPEND(From, Length) { \
1166 if (Dest + (Length) > DestEnd) \
1167 goto too_long; \
1168 memcpy(Dest, From, (Length) * sizeof(TCHAR)); \
1169 Dest += Length; }
1170 #define APPEND1(Char) { \
1171 if (Dest >= DestEnd) \
1172 goto too_long; \
1173 *Dest++ = Char; }
1174
1175 TCHAR *DestEnd = Dest + CMDLINE_LENGTH - 1;
1176 const TCHAR *Var;
1177 int VarLength;
1178 TCHAR *SubstStart;
1179 TCHAR EndChr;
1180 while (*Src)
1181 {
1182 if (*Src != Delim)
1183 {
1184 APPEND1(*Src++)
1185 continue;
1186 }
1187
1188 Src++;
1189 if (bc && Delim == _T('%'))
1190 {
1191 UINT NameLen;
1192 Var = GetBatchVar(Src, &NameLen);
1193 if (Var != NULL)
1194 {
1195 VarLength = _tcslen(Var);
1196 APPEND(Var, VarLength)
1197 Src += NameLen;
1198 continue;
1199 }
1200 }
1201
1202 /* Find the end of the variable name. A colon (:) will usually
1203 * end the name and begin the optional modifier, but not if it
1204 * is immediately followed by the delimiter (%VAR:%). */
1205 SubstStart = Src;
1206 while (*Src != Delim && !(*Src == _T(':') && Src[1] != Delim))
1207 {
1208 if (!*Src)
1209 goto bad_subst;
1210 Src++;
1211 }
1212
1213 EndChr = *Src;
1214 *Src = _T('\0');
1215 Var = GetEnvVarOrSpecial(SubstStart);
1216 *Src++ = EndChr;
1217 if (Var == NULL)
1218 {
1219 /* In a batch file, %NONEXISTENT% "expands" to an empty string */
1220 if (bc)
1221 continue;
1222 goto bad_subst;
1223 }
1224 VarLength = _tcslen(Var);
1225
1226 if (EndChr == Delim)
1227 {
1228 /* %VAR% - use as-is */
1229 APPEND(Var, VarLength)
1230 }
1231 else if (*Src == _T('~'))
1232 {
1233 /* %VAR:~[start][,length]% - substring
1234 * Negative values are offsets from the end */
1235 int Start = _tcstol(Src + 1, &Src, 0);
1236 int End = VarLength;
1237 if (Start < 0)
1238 Start += VarLength;
1239 Start = max(Start, 0);
1240 Start = min(Start, VarLength);
1241 if (*Src == _T(','))
1242 {
1243 End = _tcstol(Src + 1, &Src, 0);
1244 End += (End < 0) ? VarLength : Start;
1245 End = max(End, Start);
1246 End = min(End, VarLength);
1247 }
1248 if (*Src++ != Delim)
1249 goto bad_subst;
1250 APPEND(&Var[Start], End - Start);
1251 }
1252 else
1253 {
1254 /* %VAR:old=new% - replace all occurrences of old with new
1255 * %VAR:*old=new% - replace first occurrence only,
1256 * and remove everything before it */
1257 TCHAR *Old, *New;
1258 DWORD OldLength, NewLength;
1259 BOOL Star = FALSE;
1260 int LastMatch = 0, i = 0;
1261
1262 if (*Src == _T('*'))
1263 {
1264 Star = TRUE;
1265 Src++;
1266 }
1267
1268 /* the string to replace may contain the delimiter */
1269 Src = _tcschr(Old = Src, _T('='));
1270 if (Src == NULL)
1271 goto bad_subst;
1272 OldLength = Src++ - Old;
1273 if (OldLength == 0)
1274 goto bad_subst;
1275
1276 Src = _tcschr(New = Src, Delim);
1277 if (Src == NULL)
1278 goto bad_subst;
1279 NewLength = Src++ - New;
1280
1281 while (i < VarLength)
1282 {
1283 if (_tcsnicmp(&Var[i], Old, OldLength) == 0)
1284 {
1285 if (!Star)
1286 APPEND(&Var[LastMatch], i - LastMatch)
1287 APPEND(New, NewLength)
1288 i += OldLength;
1289 LastMatch = i;
1290 if (Star)
1291 break;
1292 continue;
1293 }
1294 i++;
1295 }
1296 APPEND(&Var[LastMatch], VarLength - LastMatch)
1297 }
1298 continue;
1299
1300 bad_subst:
1301 Src = SubstStart;
1302 if (!bc)
1303 APPEND1(Delim)
1304 }
1305 *Dest = _T('\0');
1306 return TRUE;
1307 too_long:
1308 ConOutResPrintf(STRING_ALIAS_ERROR);
1309 nErrorLevel = 9023;
1310 return FALSE;
1311 #undef APPEND
1312 #undef APPEND1
1313 }
1314
1315 /* Search the list of FOR contexts for a variable */
1316 static LPTSTR FindForVar(TCHAR Var, BOOL *IsParam0)
1317 {
1318 FOR_CONTEXT *Ctx;
1319 *IsParam0 = FALSE;
1320 for (Ctx = fc; Ctx != NULL; Ctx = Ctx->prev)
1321 {
1322 if ((UINT)(Var - Ctx->firstvar) < Ctx->varcount)
1323 return Ctx->values[Var - Ctx->firstvar];
1324 }
1325 return NULL;
1326 }
1327
1328 BOOL
1329 SubstituteForVars(TCHAR *Src, TCHAR *Dest)
1330 {
1331 TCHAR *DestEnd = &Dest[CMDLINE_LENGTH - 1];
1332 while (*Src)
1333 {
1334 if (Src[0] == _T('%'))
1335 {
1336 BOOL Dummy;
1337 LPTSTR End = &Src[2];
1338 LPTSTR Value = NULL;
1339
1340 if (Src[1] == _T('~'))
1341 Value = GetEnhancedVar(&End, FindForVar);
1342
1343 if (!Value)
1344 Value = FindForVar(Src[1], &Dummy);
1345
1346 if (Value)
1347 {
1348 if (Dest + _tcslen(Value) > DestEnd)
1349 return FALSE;
1350 Dest = _stpcpy(Dest, Value);
1351 Src = End;
1352 continue;
1353 }
1354 }
1355 /* Not a variable; just copy the character */
1356 if (Dest >= DestEnd)
1357 return FALSE;
1358 *Dest++ = *Src++;
1359 }
1360 *Dest = _T('\0');
1361 return TRUE;
1362 }
1363
1364 LPTSTR
1365 DoDelayedExpansion(LPTSTR Line)
1366 {
1367 TCHAR Buf1[CMDLINE_LENGTH];
1368 TCHAR Buf2[CMDLINE_LENGTH];
1369
1370 /* First, substitute FOR variables */
1371 if (!SubstituteForVars(Line, Buf1))
1372 return NULL;
1373
1374 if (!bDelayedExpansion || !_tcschr(Buf1, _T('!')))
1375 return cmd_dup(Buf1);
1376
1377 /* FIXME: Delayed substitutions actually aren't quite the same as
1378 * immediate substitutions. In particular, it's possible to escape
1379 * the exclamation point using ^. */
1380 if (!SubstituteVars(Buf1, Buf2, _T('!')))
1381 return NULL;
1382 return cmd_dup(Buf2);
1383 }
1384
1385
1386 /*
1387 * do the prompt/input/process loop
1388 *
1389 */
1390
1391 BOOL
1392 ReadLine(TCHAR *commandline, BOOL bMore)
1393 {
1394 TCHAR readline[CMDLINE_LENGTH];
1395 LPTSTR ip;
1396
1397 /* if no batch input then... */
1398 if (bc == NULL)
1399 {
1400 if (bMore)
1401 {
1402 ConOutResPrintf(STRING_MORE);
1403 }
1404 else
1405 {
1406 /* JPP 19980807 - if echo off, don't print prompt */
1407 if (bEcho)
1408 {
1409 if (!bIgnoreEcho)
1410 ConOutChar(_T('\n'));
1411 PrintPrompt();
1412 }
1413 }
1414
1415 if (!ReadCommand(readline, CMDLINE_LENGTH - 1))
1416 {
1417 bExit = TRUE;
1418 return FALSE;
1419 }
1420
1421 if (CheckCtrlBreak(BREAK_INPUT))
1422 {
1423 ConOutChar(_T('\n'));
1424 return FALSE;
1425 }
1426 ip = readline;
1427 }
1428 else
1429 {
1430 ip = ReadBatchLine();
1431 if (!ip)
1432 return FALSE;
1433 }
1434
1435 return SubstituteVars(ip, commandline, _T('%'));
1436 }
1437
1438 static VOID
1439 ProcessInput(VOID)
1440 {
1441 PARSED_COMMAND *Cmd;
1442
1443 while (!bCanExit || !bExit)
1444 {
1445 Cmd = ParseCommand(NULL);
1446 if (!Cmd)
1447 continue;
1448
1449 ExecuteCommand(Cmd);
1450 FreeCommand(Cmd);
1451 }
1452 }
1453
1454
1455 /*
1456 * control-break handler.
1457 */
1458 BOOL WINAPI BreakHandler(DWORD dwCtrlType)
1459 {
1460 DWORD dwWritten;
1461 INPUT_RECORD rec;
1462 static BOOL SelfGenerated = FALSE;
1463
1464 if ((dwCtrlType != CTRL_C_EVENT) &&
1465 (dwCtrlType != CTRL_BREAK_EVENT))
1466 {
1467 return FALSE;
1468 }
1469 else
1470 {
1471 if (SelfGenerated)
1472 {
1473 SelfGenerated = FALSE;
1474 return TRUE;
1475 }
1476 }
1477
1478 if (!TryEnterCriticalSection(&ChildProcessRunningLock))
1479 {
1480 SelfGenerated = TRUE;
1481 GenerateConsoleCtrlEvent (dwCtrlType, 0);
1482 return TRUE;
1483 }
1484 else
1485 {
1486 LeaveCriticalSection(&ChildProcessRunningLock);
1487 }
1488
1489 rec.EventType = KEY_EVENT;
1490 rec.Event.KeyEvent.bKeyDown = TRUE;
1491 rec.Event.KeyEvent.wRepeatCount = 1;
1492 rec.Event.KeyEvent.wVirtualKeyCode = _T('C');
1493 rec.Event.KeyEvent.wVirtualScanCode = _T('C') - 35;
1494 rec.Event.KeyEvent.uChar.AsciiChar = _T('C');
1495 rec.Event.KeyEvent.uChar.UnicodeChar = _T('C');
1496 rec.Event.KeyEvent.dwControlKeyState = RIGHT_CTRL_PRESSED;
1497
1498 WriteConsoleInput(ConStreamGetOSHandle(StdIn),
1499 &rec,
1500 1,
1501 &dwWritten);
1502
1503 bCtrlBreak = TRUE;
1504 /* FIXME: Handle batch files */
1505
1506 //ConOutPrintf(_T("^C"));
1507
1508 return TRUE;
1509 }
1510
1511
1512 VOID AddBreakHandler(VOID)
1513 {
1514 SetConsoleCtrlHandler(BreakHandler, TRUE);
1515 }
1516
1517
1518 VOID RemoveBreakHandler(VOID)
1519 {
1520 SetConsoleCtrlHandler(BreakHandler, FALSE);
1521 }
1522
1523
1524 /*
1525 * show commands and options that are available.
1526 *
1527 */
1528 #if 0
1529 static VOID
1530 ShowCommands(VOID)
1531 {
1532 /* print command list */
1533 ConOutResPuts(STRING_CMD_HELP1);
1534 PrintCommandList();
1535
1536 /* print feature list */
1537 ConOutResPuts(STRING_CMD_HELP2);
1538
1539 #ifdef FEATURE_ALIASES
1540 ConOutResPuts(STRING_CMD_HELP3);
1541 #endif
1542 #ifdef FEATURE_HISTORY
1543 ConOutResPuts(STRING_CMD_HELP4);
1544 #endif
1545 #ifdef FEATURE_UNIX_FILENAME_COMPLETION
1546 ConOutResPuts(STRING_CMD_HELP5);
1547 #endif
1548 #ifdef FEATURE_DIRECTORY_STACK
1549 ConOutResPuts(STRING_CMD_HELP6);
1550 #endif
1551 #ifdef FEATURE_REDIRECTION
1552 ConOutResPuts(STRING_CMD_HELP7);
1553 #endif
1554 ConOutChar(_T('\n'));
1555 }
1556 #endif
1557
1558
1559 static VOID
1560 LoadRegistrySettings(HKEY hKeyRoot)
1561 {
1562 LONG lRet;
1563 HKEY hKey;
1564 DWORD dwType, len;
1565 /*
1566 * Buffer big enough to hold the string L"4294967295",
1567 * corresponding to the literal 0xFFFFFFFF (MAX_ULONG) in decimal.
1568 */
1569 DWORD Buffer[6];
1570
1571 lRet = RegOpenKeyEx(hKeyRoot,
1572 _T("Software\\Microsoft\\Command Processor"),
1573 0,
1574 KEY_QUERY_VALUE,
1575 &hKey);
1576 if (lRet != ERROR_SUCCESS)
1577 return;
1578
1579 #ifdef INCLUDE_CMD_COLOR
1580 len = sizeof(Buffer);
1581 lRet = RegQueryValueEx(hKey,
1582 _T("DefaultColor"),
1583 NULL,
1584 &dwType,
1585 (LPBYTE)&Buffer,
1586 &len);
1587 if (lRet == ERROR_SUCCESS)
1588 {
1589 /* Overwrite the default attributes */
1590 if (dwType == REG_DWORD)
1591 wDefColor = (WORD)*(PDWORD)Buffer;
1592 else if (dwType == REG_SZ)
1593 wDefColor = (WORD)_tcstol((PTSTR)Buffer, NULL, 0);
1594 }
1595 // else, use the default attributes retrieved before.
1596 #endif
1597
1598 #if 0
1599 len = sizeof(Buffer);
1600 lRet = RegQueryValueEx(hKey,
1601 _T("DisableUNCCheck"),
1602 NULL,
1603 &dwType,
1604 (LPBYTE)&Buffer,
1605 &len);
1606 if (lRet == ERROR_SUCCESS)
1607 {
1608 /* Overwrite the default setting */
1609 if (dwType == REG_DWORD)
1610 bDisableUNCCheck = !!*(PDWORD)Buffer;
1611 else if (dwType == REG_SZ)
1612 bDisableUNCCheck = (_ttol((PTSTR)Buffer) == 1);
1613 }
1614 // else, use the default setting set globally.
1615 #endif
1616
1617 len = sizeof(Buffer);
1618 lRet = RegQueryValueEx(hKey,
1619 _T("DelayedExpansion"),
1620 NULL,
1621 &dwType,
1622 (LPBYTE)&Buffer,
1623 &len);
1624 if (lRet == ERROR_SUCCESS)
1625 {
1626 /* Overwrite the default setting */
1627 if (dwType == REG_DWORD)
1628 bDelayedExpansion = !!*(PDWORD)Buffer;
1629 else if (dwType == REG_SZ)
1630 bDelayedExpansion = (_ttol((PTSTR)Buffer) == 1);
1631 }
1632 // else, use the default setting set globally.
1633
1634 len = sizeof(Buffer);
1635 lRet = RegQueryValueEx(hKey,
1636 _T("EnableExtensions"),
1637 NULL,
1638 &dwType,
1639 (LPBYTE)&Buffer,
1640 &len);
1641 if (lRet == ERROR_SUCCESS)
1642 {
1643 /* Overwrite the default setting */
1644 if (dwType == REG_DWORD)
1645 bEnableExtensions = !!*(PDWORD)Buffer;
1646 else if (dwType == REG_SZ)
1647 bEnableExtensions = (_ttol((PTSTR)Buffer) == 1);
1648 }
1649 // else, use the default setting set globally.
1650
1651 len = sizeof(Buffer);
1652 lRet = RegQueryValueEx(hKey,
1653 _T("CompletionChar"),
1654 NULL,
1655 &dwType,
1656 (LPBYTE)&Buffer,
1657 &len);
1658 if (lRet == ERROR_SUCCESS)
1659 {
1660 /* Overwrite the default setting */
1661 if (dwType == REG_DWORD)
1662 AutoCompletionChar = (TCHAR)*(PDWORD)Buffer;
1663 else if (dwType == REG_SZ)
1664 AutoCompletionChar = (TCHAR)_tcstol((PTSTR)Buffer, NULL, 0);
1665 }
1666 // else, use the default setting set globally.
1667
1668 /* Validity check */
1669 if (IS_COMPLETION_DISABLED(AutoCompletionChar))
1670 {
1671 /* Disable autocompletion */
1672 AutoCompletionChar = 0x20;
1673 }
1674
1675 len = sizeof(Buffer);
1676 lRet = RegQueryValueEx(hKey,
1677 _T("PathCompletionChar"),
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 PathCompletionChar = (TCHAR)*(PDWORD)Buffer;
1687 else if (dwType == REG_SZ)
1688 PathCompletionChar = (TCHAR)_tcstol((PTSTR)Buffer, NULL, 0);
1689 }
1690 // else, use the default setting set globally.
1691
1692 /* Validity check */
1693 if (IS_COMPLETION_DISABLED(PathCompletionChar))
1694 {
1695 /* Disable autocompletion */
1696 PathCompletionChar = 0x20;
1697 }
1698
1699 /* Adjust completion chars */
1700 if (PathCompletionChar >= 0x20 && AutoCompletionChar < 0x20)
1701 PathCompletionChar = AutoCompletionChar;
1702 else if (AutoCompletionChar >= 0x20 && PathCompletionChar < 0x20)
1703 AutoCompletionChar = PathCompletionChar;
1704
1705 RegCloseKey(hKey);
1706 }
1707
1708 static VOID
1709 ExecuteAutoRunFile(HKEY hKeyRoot)
1710 {
1711 LONG lRet;
1712 HKEY hKey;
1713 DWORD dwType, len;
1714 TCHAR AutoRun[2048];
1715
1716 lRet = RegOpenKeyEx(hKeyRoot,
1717 _T("Software\\Microsoft\\Command Processor"),
1718 0,
1719 KEY_QUERY_VALUE,
1720 &hKey);
1721 if (lRet != ERROR_SUCCESS)
1722 return;
1723
1724 len = sizeof(AutoRun);
1725 lRet = RegQueryValueEx(hKey,
1726 _T("AutoRun"),
1727 NULL,
1728 &dwType,
1729 (LPBYTE)&AutoRun,
1730 &len);
1731 if ((lRet == ERROR_SUCCESS) && (dwType == REG_EXPAND_SZ || dwType == REG_SZ))
1732 {
1733 if (*AutoRun)
1734 ParseCommandLine(AutoRun);
1735 }
1736
1737 RegCloseKey(hKey);
1738 }
1739
1740 /* Get the command that comes after a /C or /K switch */
1741 static VOID
1742 GetCmdLineCommand(TCHAR *commandline, TCHAR *ptr, BOOL AlwaysStrip)
1743 {
1744 TCHAR *LastQuote;
1745
1746 while (_istspace(*ptr))
1747 ptr++;
1748
1749 /* Remove leading quote, find final quote */
1750 if (*ptr == _T('"') &&
1751 (LastQuote = _tcsrchr(++ptr, _T('"'))) != NULL)
1752 {
1753 TCHAR *Space;
1754 /* Under certain circumstances, all quotes are preserved.
1755 * CMD /? documents these conditions as follows:
1756 * 1. No /S switch
1757 * 2. Exactly two quotes
1758 * 3. No "special characters" between the quotes
1759 * (CMD /? says &<>()@^| but parentheses did not
1760 * trigger this rule when I tested them.)
1761 * 4. Whitespace exists between the quotes
1762 * 5. Enclosed string is an executable filename
1763 */
1764 *LastQuote = _T('\0');
1765 for (Space = ptr + 1; Space < LastQuote; Space++)
1766 {
1767 if (_istspace(*Space)) /* Rule 4 */
1768 {
1769 if (!AlwaysStrip && /* Rule 1 */
1770 !_tcspbrk(ptr, _T("\"&<>@^|")) && /* Rules 2, 3 */
1771 SearchForExecutable(ptr, commandline)) /* Rule 5 */
1772 {
1773 /* All conditions met: preserve both the quotes */
1774 *LastQuote = _T('"');
1775 _tcscpy(commandline, ptr - 1);
1776 return;
1777 }
1778 break;
1779 }
1780 }
1781
1782 /* The conditions were not met: remove both the
1783 * leading quote and the last quote */
1784 _tcscpy(commandline, ptr);
1785 _tcscpy(&commandline[LastQuote - ptr], LastQuote + 1);
1786 return;
1787 }
1788
1789 /* No quotes; just copy */
1790 _tcscpy(commandline, ptr);
1791 }
1792
1793
1794 /*
1795 * Set up global initializations and process parameters
1796 */
1797 static VOID
1798 Initialize(VOID)
1799 {
1800 HMODULE NtDllModule;
1801 TCHAR commandline[CMDLINE_LENGTH];
1802 TCHAR ModuleName[_MAX_PATH + 1];
1803 // INT nExitCode;
1804
1805 HANDLE hIn, hOut;
1806
1807 TCHAR *ptr, *cmdLine, option = 0;
1808 BOOL AlwaysStrip = FALSE;
1809 BOOL AutoRun = TRUE;
1810
1811 /* Get version information */
1812 InitOSVersion();
1813
1814 /* Some people like to run ReactOS cmd.exe on Win98, it helps in the
1815 * build process. So don't link implicitly against ntdll.dll, load it
1816 * dynamically instead */
1817 NtDllModule = GetModuleHandle(TEXT("ntdll.dll"));
1818 if (NtDllModule != NULL)
1819 {
1820 NtQueryInformationProcessPtr = (NtQueryInformationProcessProc)GetProcAddress(NtDllModule, "NtQueryInformationProcess");
1821 NtReadVirtualMemoryPtr = (NtReadVirtualMemoryProc)GetProcAddress(NtDllModule, "NtReadVirtualMemory");
1822 }
1823
1824 /* Load the registry settings */
1825 LoadRegistrySettings(HKEY_LOCAL_MACHINE);
1826 LoadRegistrySettings(HKEY_CURRENT_USER);
1827
1828 /* Initialize our locale */
1829 InitLocale();
1830
1831 /* Initialize prompt support */
1832 InitPrompt();
1833
1834 #ifdef FEATURE_DIR_STACK
1835 /* Initialize directory stack */
1836 InitDirectoryStack();
1837 #endif
1838
1839 #ifdef FEATURE_HISTORY
1840 /* Initialize history */
1841 InitHistory();
1842 #endif
1843
1844 /* Set COMSPEC environment variable */
1845 if (GetModuleFileName(NULL, ModuleName, ARRAYSIZE(ModuleName)) != 0)
1846 {
1847 ModuleName[_MAX_PATH] = _T('\0');
1848 SetEnvironmentVariable (_T("COMSPEC"), ModuleName);
1849 }
1850
1851 /* Add ctrl break handler */
1852 AddBreakHandler();
1853
1854 /* Set our default console mode */
1855 hOut = ConStreamGetOSHandle(StdOut);
1856 hIn = ConStreamGetOSHandle(StdIn);
1857 SetConsoleMode(hOut, 0); // Reinitialize the console output mode
1858 SetConsoleMode(hOut, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
1859 SetConsoleMode(hIn , ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
1860
1861 cmdLine = GetCommandLine();
1862 TRACE ("[command args: %s]\n", debugstr_aw(cmdLine));
1863
1864 for (ptr = cmdLine; *ptr; ptr++)
1865 {
1866 if (*ptr == _T('/'))
1867 {
1868 option = _totupper(ptr[1]);
1869 if (option == _T('?'))
1870 {
1871 ConOutResPaging(TRUE,STRING_CMD_HELP8);
1872 nErrorLevel = 1;
1873 bExit = TRUE;
1874 return;
1875 }
1876 else if (option == _T('P'))
1877 {
1878 if (!IsExistingFile (_T("\\autoexec.bat")))
1879 {
1880 #ifdef INCLUDE_CMD_DATE
1881 cmd_date (_T(""));
1882 #endif
1883 #ifdef INCLUDE_CMD_TIME
1884 cmd_time (_T(""));
1885 #endif
1886 }
1887 else
1888 {
1889 ParseCommandLine (_T("\\autoexec.bat"));
1890 }
1891 bCanExit = FALSE;
1892 }
1893 else if (option == _T('A'))
1894 {
1895 OutputStreamMode = AnsiText;
1896 }
1897 else if (option == _T('C') || option == _T('K') || option == _T('R'))
1898 {
1899 /* Remainder of command line is a command to be run */
1900 break;
1901 }
1902 else if (option == _T('D'))
1903 {
1904 AutoRun = FALSE;
1905 }
1906 else if (option == _T('Q'))
1907 {
1908 bDisableBatchEcho = TRUE;
1909 }
1910 else if (option == _T('S'))
1911 {
1912 AlwaysStrip = TRUE;
1913 }
1914 #ifdef INCLUDE_CMD_COLOR
1915 else if (!_tcsnicmp(ptr, _T("/T:"), 3))
1916 {
1917 /* Process /T (color) argument; overwrite any previous settings */
1918 wDefColor = (WORD)_tcstoul(&ptr[3], &ptr, 16);
1919 }
1920 #endif
1921 else if (option == _T('U'))
1922 {
1923 OutputStreamMode = UTF16Text;
1924 }
1925 else if (option == _T('V'))
1926 {
1927 // FIXME: Check validity of the parameter given to V !
1928 bDelayedExpansion = _tcsnicmp(&ptr[2], _T(":OFF"), 4);
1929 }
1930 else if (option == _T('E'))
1931 {
1932 // FIXME: Check validity of the parameter given to E !
1933 bEnableExtensions = _tcsnicmp(&ptr[2], _T(":OFF"), 4);
1934 }
1935 else if (option == _T('X'))
1936 {
1937 /* '/X' is identical to '/E:ON' */
1938 bEnableExtensions = TRUE;
1939 }
1940 else if (option == _T('Y'))
1941 {
1942 /* '/Y' is identical to '/E:OFF' */
1943 bEnableExtensions = FALSE;
1944 }
1945 }
1946 }
1947
1948 #ifdef INCLUDE_CMD_COLOR
1949 if (wDefColor == 0)
1950 {
1951 /*
1952 * If we still do not have the console colour attribute set,
1953 * retrieve the default one.
1954 */
1955 ConGetDefaultAttributes(&wDefColor);
1956 }
1957
1958 if (wDefColor != 0)
1959 ConSetScreenColor(ConStreamGetOSHandle(StdOut), wDefColor, TRUE);
1960 #endif
1961
1962 /* Reset the output Standard Streams translation modes and codepage caches */
1963 // ConStreamSetMode(StdIn , OutputStreamMode, InputCodePage );
1964 ConStreamSetMode(StdOut, OutputStreamMode, OutputCodePage);
1965 ConStreamSetMode(StdErr, OutputStreamMode, OutputCodePage);
1966
1967 if (!*ptr)
1968 {
1969 /* If neither /C or /K was given, display a simple version string */
1970 ConOutChar(_T('\n'));
1971 ConOutResPrintf(STRING_REACTOS_VERSION,
1972 _T(KERNEL_VERSION_STR),
1973 _T(KERNEL_VERSION_BUILD_STR));
1974 ConOutPuts(_T("(C) Copyright 1998-") _T(COPYRIGHT_YEAR) _T(" ReactOS Team.\n"));
1975 }
1976
1977 if (AutoRun)
1978 {
1979 ExecuteAutoRunFile(HKEY_LOCAL_MACHINE);
1980 ExecuteAutoRunFile(HKEY_CURRENT_USER);
1981 }
1982
1983 if (*ptr)
1984 {
1985 /* Do the /C or /K command */
1986 GetCmdLineCommand(commandline, &ptr[2], AlwaysStrip);
1987 bWaitForCommand = TRUE;
1988 /* nExitCode = */ ParseCommandLine(commandline);
1989 bWaitForCommand = FALSE;
1990 if (option != _T('K'))
1991 {
1992 // nErrorLevel = nExitCode;
1993 bExit = TRUE;
1994 }
1995 }
1996 }
1997
1998
1999 static VOID Cleanup(VOID)
2000 {
2001 /* Run cmdexit.bat */
2002 if (IsExistingFile(_T("cmdexit.bat")))
2003 {
2004 ConErrResPuts(STRING_CMD_ERROR5);
2005 ParseCommandLine(_T("cmdexit.bat"));
2006 }
2007 else if (IsExistingFile(_T("\\cmdexit.bat")))
2008 {
2009 ConErrResPuts(STRING_CMD_ERROR5);
2010 ParseCommandLine(_T("\\cmdexit.bat"));
2011 }
2012
2013 #ifdef FEATURE_DIRECTORY_STACK
2014 /* Destroy directory stack */
2015 DestroyDirectoryStack();
2016 #endif
2017
2018 #ifdef FEATURE_HISTORY
2019 CleanHistory();
2020 #endif
2021
2022 /* Free GetEnvVar's buffer */
2023 GetEnvVar(NULL);
2024
2025 /* Remove ctrl break handler */
2026 RemoveBreakHandler();
2027
2028 /* Restore the default console mode */
2029 SetConsoleMode(ConStreamGetOSHandle(StdIn),
2030 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
2031 SetConsoleMode(ConStreamGetOSHandle(StdOut),
2032 ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
2033
2034 DeleteCriticalSection(&ChildProcessRunningLock);
2035 }
2036
2037 /*
2038 * main function
2039 */
2040 int _tmain(int argc, const TCHAR *argv[])
2041 {
2042 TCHAR startPath[MAX_PATH];
2043
2044 InitializeCriticalSection(&ChildProcessRunningLock);
2045 lpOriginalEnvironment = DuplicateEnvironment();
2046
2047 GetCurrentDirectory(ARRAYSIZE(startPath), startPath);
2048 _tchdir(startPath);
2049
2050 SetFileApisToOEM();
2051 InputCodePage = GetConsoleCP();
2052 OutputCodePage = GetConsoleOutputCP();
2053
2054 /* Initialize the Console Standard Streams */
2055 ConStreamInit(StdIn , GetStdHandle(STD_INPUT_HANDLE) , /*OutputStreamMode*/ AnsiText, InputCodePage);
2056 ConStreamInit(StdOut, GetStdHandle(STD_OUTPUT_HANDLE), OutputStreamMode, OutputCodePage);
2057 ConStreamInit(StdErr, GetStdHandle(STD_ERROR_HANDLE) , OutputStreamMode, OutputCodePage);
2058
2059 CMD_ModuleHandle = GetModuleHandle(NULL);
2060
2061 /* Perform general initialization, parse switches on command-line */
2062 Initialize();
2063
2064 /* Call prompt routine */
2065 ProcessInput();
2066
2067 /* Do the cleanup */
2068 Cleanup();
2069
2070 cmd_free(lpOriginalEnvironment);
2071
2072 cmd_exit(nErrorLevel);
2073 return nErrorLevel;
2074 }
2075
2076 /* EOF */