- Update to trunk
[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 carrage 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 memmory alloc ProcessInput, the error
141 * handling for memmory handling need to be improve
142 */
143
144 #include <precomp.h>
145
146 #ifndef NT_SUCCESS
147 #define NT_SUCCESS(StatCode) ((NTSTATUS)(StatCode) >= 0)
148 #endif
149
150 typedef NTSTATUS (WINAPI *NtQueryInformationProcessProc)(HANDLE, PROCESSINFOCLASS,
151 PVOID, ULONG, PULONG);
152 typedef NTSTATUS (WINAPI *NtReadVirtualMemoryProc)(HANDLE, PVOID, PVOID, ULONG, PULONG);
153
154 BOOL bExit = FALSE; /* indicates EXIT was typed */
155 BOOL bCanExit = TRUE; /* indicates if this shell is exitable */
156 BOOL bCtrlBreak = FALSE; /* Ctrl-Break or Ctrl-C hit */
157 BOOL bIgnoreEcho = FALSE; /* Set this to TRUE to prevent a newline, when executing a command */
158 INT nErrorLevel = 0; /* Errorlevel of last launched external program */
159 CRITICAL_SECTION ChildProcessRunningLock;
160 BOOL bUnicodeOutput = FALSE;
161 BOOL bDisableBatchEcho = FALSE;
162 BOOL bDelayedExpansion = FALSE;
163 DWORD dwChildProcessId = 0;
164 OSVERSIONINFO osvi;
165 HANDLE hIn;
166 HANDLE hOut;
167 LPTSTR lpOriginalEnvironment;
168 HANDLE CMD_ModuleHandle;
169
170 static NtQueryInformationProcessProc NtQueryInformationProcessPtr = NULL;
171 static NtReadVirtualMemoryProc NtReadVirtualMemoryPtr = NULL;
172
173 #ifdef INCLUDE_CMD_COLOR
174 WORD wDefColor; /* default color */
175 #endif
176
177 /*
178 * convert
179 *
180 * insert commas into a number
181 */
182 INT
183 ConvertULargeInteger(ULONGLONG num, LPTSTR des, UINT len, BOOL bPutSeperator)
184 {
185 TCHAR temp[39]; /* maximum length with nNumberGroups == 1 */
186 UINT n, iTarget;
187
188 if (len <= 1)
189 return 0;
190
191 n = 0;
192 iTarget = nNumberGroups;
193 if (!nNumberGroups)
194 bPutSeperator = FALSE;
195
196 do
197 {
198 if (iTarget == n && bPutSeperator)
199 {
200 iTarget += nNumberGroups + 1;
201 temp[38 - n++] = cThousandSeparator;
202 }
203 temp[38 - n++] = (TCHAR)(num % 10) + _T('0');
204 num /= 10;
205 } while (num > 0);
206 if (n > len-1)
207 n = len-1;
208
209 memcpy(des, temp + 39 - n, n * sizeof(TCHAR));
210 des[n] = _T('\0');
211
212 return n;
213 }
214
215 /*
216 * Is a process a console process?
217 */
218 static BOOL IsConsoleProcess(HANDLE Process)
219 {
220 NTSTATUS Status;
221 PROCESS_BASIC_INFORMATION Info;
222 PEB ProcessPeb;
223 ULONG BytesRead;
224
225 if (NULL == NtQueryInformationProcessPtr || NULL == NtReadVirtualMemoryPtr)
226 {
227 return TRUE;
228 }
229
230 Status = NtQueryInformationProcessPtr (
231 Process, ProcessBasicInformation,
232 &Info, sizeof(PROCESS_BASIC_INFORMATION), NULL);
233 if (! NT_SUCCESS(Status))
234 {
235 WARN ("NtQueryInformationProcess failed with status %08x\n", Status);
236 return TRUE;
237 }
238 Status = NtReadVirtualMemoryPtr (
239 Process, Info.PebBaseAddress, &ProcessPeb,
240 sizeof(PEB), &BytesRead);
241 if (! NT_SUCCESS(Status) || sizeof(PEB) != BytesRead)
242 {
243 WARN ("Couldn't read virt mem status %08x bytes read %lu\n", Status, BytesRead);
244 return TRUE;
245 }
246
247 return IMAGE_SUBSYSTEM_WINDOWS_CUI == ProcessPeb.ImageSubsystem;
248 }
249
250
251
252 #ifdef _UNICODE
253 #define SHELLEXECUTETEXT "ShellExecuteExW"
254 #else
255 #define SHELLEXECUTETEXT "ShellExecuteExA"
256 #endif
257
258 typedef BOOL (WINAPI *MYEX)(LPSHELLEXECUTEINFO lpExecInfo);
259
260 HANDLE RunFile(DWORD flags, LPTSTR filename, LPTSTR params,
261 LPTSTR directory, INT show)
262 {
263 SHELLEXECUTEINFO sei;
264 HMODULE hShell32;
265 MYEX hShExt;
266 BOOL ret;
267
268 TRACE ("RunFile(%s)\n", debugstr_aw(filename));
269 hShell32 = LoadLibrary(_T("SHELL32.DLL"));
270 if (!hShell32)
271 {
272 WARN ("RunFile: couldn't load SHELL32.DLL!\n");
273 return NULL;
274 }
275
276 hShExt = (MYEX)(FARPROC)GetProcAddress(hShell32, SHELLEXECUTETEXT);
277 if (!hShExt)
278 {
279 WARN ("RunFile: couldn't find ShellExecuteExA/W in SHELL32.DLL!\n");
280 FreeLibrary(hShell32);
281 return NULL;
282 }
283
284 TRACE ("RunFile: ShellExecuteExA/W is at %x\n", hShExt);
285
286 memset(&sei, 0, sizeof sei);
287 sei.cbSize = sizeof sei;
288 sei.fMask = flags;
289 sei.lpFile = filename;
290 sei.lpParameters = params;
291 sei.lpDirectory = directory;
292 sei.nShow = show;
293 ret = hShExt(&sei);
294
295 TRACE ("RunFile: ShellExecuteExA/W returned 0x%p\n", ret);
296
297 FreeLibrary(hShell32);
298 return ret ? sei.hProcess : NULL;
299 }
300
301
302
303 /*
304 * This command (in first) was not found in the command table
305 *
306 * Full - buffer to hold whole command line
307 * First - first word on command line
308 * Rest - rest of command line
309 */
310
311 static INT
312 Execute (LPTSTR Full, LPTSTR First, LPTSTR Rest, PARSED_COMMAND *Cmd)
313 {
314 TCHAR szFullName[MAX_PATH];
315 TCHAR *first, *rest, *dot;
316 TCHAR szWindowTitle[MAX_PATH];
317 DWORD dwExitCode = 0;
318 TCHAR *FirstEnd;
319 TCHAR szFullCmdLine [CMDLINE_LENGTH];
320
321 TRACE ("Execute: \'%s\' \'%s\'\n", debugstr_aw(First), debugstr_aw(Rest));
322
323 /* Though it was already parsed once, we have a different set of rules
324 for parsing before we pass to CreateProccess */
325 if (First[0] == _T('/') || (First[0] && First[1] == _T(':')))
326 {
327 /* Use the entire first word as the program name (no change) */
328 FirstEnd = First + _tcslen(First);
329 }
330 else
331 {
332 /* If present in the first word, spaces and ,;=/ end the program
333 * name and become the beginning of its parameters. */
334 BOOL bInside = FALSE;
335 for (FirstEnd = First; *FirstEnd; FirstEnd++)
336 {
337 if (!bInside && (_istspace(*FirstEnd) || _tcschr(_T(",;=/"), *FirstEnd)))
338 break;
339 bInside ^= *FirstEnd == _T('"');
340 }
341 }
342
343 /* Copy the new first/rest into the buffer */
344 first = Full;
345 rest = &Full[FirstEnd - First + 1];
346 _tcscpy(rest, FirstEnd);
347 _tcscat(rest, Rest);
348 *FirstEnd = _T('\0');
349 _tcscpy(first, First);
350
351 /* check for a drive change */
352 if ((_istalpha (first[0])) && (!_tcscmp (first + 1, _T(":"))))
353 {
354 BOOL working = TRUE;
355 if (!SetCurrentDirectory(first))
356 /* Guess they changed disc or something, handle that gracefully and get to root */
357 {
358 TCHAR str[4];
359 str[0]=first[0];
360 str[1]=_T(':');
361 str[2]=_T('\\');
362 str[3]=0;
363 working = SetCurrentDirectory(str);
364 }
365
366 if (!working) ConErrResPuts (STRING_FREE_ERROR1);
367 return !working;
368 }
369
370 /* get the PATH environment variable and parse it */
371 /* search the PATH environment variable for the binary */
372 StripQuotes(First);
373 if (!SearchForExecutable(First, szFullName))
374 {
375 error_bad_command(first);
376 return 1;
377 }
378
379 GetConsoleTitle (szWindowTitle, MAX_PATH);
380
381 /* check if this is a .BAT or .CMD file */
382 dot = _tcsrchr (szFullName, _T('.'));
383 if (dot && (!_tcsicmp (dot, _T(".bat")) || !_tcsicmp (dot, _T(".cmd"))))
384 {
385 while (*rest == _T(' '))
386 rest++;
387 TRACE ("[BATCH: %s %s]\n", debugstr_aw(szFullName), debugstr_aw(rest));
388 dwExitCode = Batch(szFullName, first, rest, Cmd);
389 }
390 else
391 {
392 /* exec the program */
393 PROCESS_INFORMATION prci;
394 STARTUPINFO stui;
395
396 /* build command line for CreateProcess(): FullName + " " + rest */
397 _tcscpy(szFullCmdLine, szFullName);
398
399 if (*rest)
400 {
401 _tcsncat(szFullCmdLine, _T(" "), CMDLINE_LENGTH - _tcslen(szFullCmdLine));
402 _tcsncat(szFullCmdLine, rest, CMDLINE_LENGTH - _tcslen(szFullCmdLine));
403 }
404
405 TRACE ("[EXEC: %s]\n", debugstr_aw(szFullCmdLine));
406
407 /* fill startup info */
408 memset (&stui, 0, sizeof (STARTUPINFO));
409 stui.cb = sizeof (STARTUPINFO);
410 stui.dwFlags = STARTF_USESHOWWINDOW;
411 stui.wShowWindow = SW_SHOWDEFAULT;
412
413 // return console to standard mode
414 SetConsoleMode (GetStdHandle(STD_INPUT_HANDLE),
415 ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT );
416
417 if (CreateProcess (szFullName,
418 szFullCmdLine,
419 NULL,
420 NULL,
421 TRUE,
422 0, /* CREATE_NEW_PROCESS_GROUP */
423 NULL,
424 NULL,
425 &stui,
426 &prci))
427
428 {
429 CloseHandle(prci.hThread);
430 }
431 else
432 {
433 // See if we can run this with ShellExecute() ie myfile.xls
434 prci.hProcess = RunFile(SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE,
435 szFullName,
436 rest,
437 NULL,
438 SW_SHOWNORMAL);
439 }
440
441 if (prci.hProcess != NULL)
442 {
443 if (IsConsoleProcess(prci.hProcess))
444 {
445 EnterCriticalSection(&ChildProcessRunningLock);
446 dwChildProcessId = prci.dwProcessId;
447
448 WaitForSingleObject (prci.hProcess, INFINITE);
449
450 LeaveCriticalSection(&ChildProcessRunningLock);
451
452 GetExitCodeProcess (prci.hProcess, &dwExitCode);
453 nErrorLevel = (INT)dwExitCode;
454 }
455 CloseHandle (prci.hProcess);
456 }
457 else
458 {
459 TRACE ("[ShellExecute failed!: %s]\n", debugstr_aw(Full));
460 error_bad_command (first);
461 dwExitCode = 1;
462 }
463
464 // restore console mode
465 SetConsoleMode (
466 GetStdHandle( STD_INPUT_HANDLE ),
467 ENABLE_PROCESSED_INPUT );
468 }
469
470 /* Get code page if it has been change */
471 InputCodePage= GetConsoleCP();
472 OutputCodePage = GetConsoleOutputCP();
473 SetConsoleTitle (szWindowTitle);
474
475 return dwExitCode;
476 }
477
478
479 /*
480 * look through the internal commands and determine whether or not this
481 * command is one of them. If it is, call the command. If not, call
482 * execute to run it as an external program.
483 *
484 * first - first word on command line
485 * rest - rest of command line
486 */
487
488 INT
489 DoCommand(LPTSTR first, LPTSTR rest, PARSED_COMMAND *Cmd)
490 {
491 TCHAR *com;
492 TCHAR *cp;
493 LPTSTR param; /* pointer to command's parameters */
494 INT cl;
495 LPCOMMAND cmdptr;
496 BOOL nointernal = FALSE;
497 INT ret;
498
499 TRACE ("DoCommand: (\'%s\' \'%s\')\n", debugstr_aw(first), debugstr_aw(rest));
500
501 /* full command line */
502 com = cmd_alloc((_tcslen(first) + _tcslen(rest) + 2) * sizeof(TCHAR));
503 if (com == NULL)
504 {
505 error_out_of_memory();
506 return 1;
507 }
508
509 /* If present in the first word, these characters end the name of an
510 * internal command and become the beginning of its parameters. */
511 cp = first + _tcscspn(first, _T("\t +,/;=[]"));
512
513 for (cl = 0; cl < (cp - first); cl++)
514 {
515 /* These characters do it too, but if one of them is present,
516 * then we check to see if the word is a file name and skip
517 * checking for internal commands if so.
518 * This allows running programs with names like "echo.exe" */
519 if (_tcschr(_T(".:\\"), first[cl]))
520 {
521 TCHAR tmp = *cp;
522 *cp = _T('\0');
523 nointernal = IsExistingFile(first);
524 *cp = tmp;
525 break;
526 }
527 }
528
529 /* Scan internal command table */
530 for (cmdptr = cmds; !nointernal && cmdptr->name; cmdptr++)
531 {
532 if (!_tcsnicmp(first, cmdptr->name, cl) && cmdptr->name[cl] == _T('\0'))
533 {
534 _tcscpy(com, first);
535 _tcscat(com, rest);
536 param = &com[cl];
537
538 /* Skip over whitespace to rest of line, exclude 'echo' command */
539 if (_tcsicmp(cmdptr->name, _T("echo")) != 0)
540 while (_istspace(*param))
541 param++;
542 ret = cmdptr->func(param);
543 cmd_free(com);
544 return ret;
545 }
546 }
547
548 ret = Execute(com, first, rest, Cmd);
549 cmd_free(com);
550 return ret;
551 }
552
553
554 /*
555 * process the command line and execute the appropriate functions
556 * full input/output redirection and piping are supported
557 */
558
559 INT ParseCommandLine (LPTSTR cmd)
560 {
561 INT Ret = 0;
562 PARSED_COMMAND *Cmd = ParseCommand(cmd);
563 if (Cmd)
564 {
565 Ret = ExecuteCommand(Cmd);
566 FreeCommand(Cmd);
567 }
568 return Ret;
569 }
570
571 /* Execute a command without waiting for it to finish. If it's an internal
572 * command or batch file, we must create a new cmd.exe process to handle it.
573 * TODO: For now, this just always creates a cmd.exe process.
574 * This works, but is inefficient for running external programs,
575 * which could just be run directly. */
576 static HANDLE
577 ExecuteAsync(PARSED_COMMAND *Cmd)
578 {
579 TCHAR CmdPath[MAX_PATH];
580 TCHAR CmdParams[CMDLINE_LENGTH], *ParamsEnd;
581 STARTUPINFO stui;
582 PROCESS_INFORMATION prci;
583
584 /* Get the path to cmd.exe */
585 GetModuleFileName(NULL, CmdPath, MAX_PATH);
586
587 /* Build the parameter string to pass to cmd.exe */
588 ParamsEnd = _stpcpy(CmdParams, _T("/S/D/C\""));
589 ParamsEnd = Unparse(Cmd, ParamsEnd, &CmdParams[CMDLINE_LENGTH - 2]);
590 if (!ParamsEnd)
591 {
592 error_out_of_memory();
593 return NULL;
594 }
595 _tcscpy(ParamsEnd, _T("\""));
596
597 memset(&stui, 0, sizeof stui);
598 stui.cb = sizeof(STARTUPINFO);
599 if (!CreateProcess(CmdPath, CmdParams, NULL, NULL, TRUE, 0,
600 NULL, NULL, &stui, &prci))
601 {
602 ErrorMessage(GetLastError(), NULL);
603 return NULL;
604 }
605
606 CloseHandle(prci.hThread);
607 return prci.hProcess;
608 }
609
610 static VOID
611 ExecutePipeline(PARSED_COMMAND *Cmd)
612 {
613 #ifdef FEATURE_REDIRECTION
614 HANDLE hInput = NULL;
615 HANDLE hOldConIn = GetStdHandle(STD_INPUT_HANDLE);
616 HANDLE hOldConOut = GetStdHandle(STD_OUTPUT_HANDLE);
617 HANDLE hProcess[MAXIMUM_WAIT_OBJECTS];
618 INT nProcesses = 0;
619 DWORD dwExitCode;
620
621 /* Do all but the last pipe command */
622 do
623 {
624 HANDLE hPipeRead, hPipeWrite;
625 if (nProcesses > (MAXIMUM_WAIT_OBJECTS - 2))
626 {
627 error_too_many_parameters(_T("|"));
628 goto failed;
629 }
630
631 /* Create the pipe that this process will write into.
632 * Make the handles non-inheritable initially, because this
633 * process shouldn't inherit the reading handle. */
634 if (!CreatePipe(&hPipeRead, &hPipeWrite, NULL, 0))
635 {
636 error_no_pipe();
637 goto failed;
638 }
639
640 /* The writing side of the pipe is STDOUT for this process */
641 SetHandleInformation(hPipeWrite, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
642 SetStdHandle(STD_OUTPUT_HANDLE, hPipeWrite);
643
644 /* Execute it (error check is done later for easier cleanup) */
645 hProcess[nProcesses] = ExecuteAsync(Cmd->Subcommands);
646 CloseHandle(hPipeWrite);
647 if (hInput)
648 CloseHandle(hInput);
649
650 /* The reading side of the pipe will be STDIN for the next process */
651 SetHandleInformation(hPipeRead, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
652 SetStdHandle(STD_INPUT_HANDLE, hPipeRead);
653 hInput = hPipeRead;
654
655 if (!hProcess[nProcesses])
656 goto failed;
657 nProcesses++;
658
659 Cmd = Cmd->Subcommands->Next;
660 } while (Cmd->Type == C_PIPE);
661
662 /* The last process uses the original STDOUT */
663 SetStdHandle(STD_OUTPUT_HANDLE, hOldConOut);
664 hProcess[nProcesses] = ExecuteAsync(Cmd);
665 if (!hProcess[nProcesses])
666 goto failed;
667 nProcesses++;
668 CloseHandle(hInput);
669 SetStdHandle(STD_INPUT_HANDLE, hOldConIn);
670
671 /* Wait for all processes to complete */
672 EnterCriticalSection(&ChildProcessRunningLock);
673 WaitForMultipleObjects(nProcesses, hProcess, TRUE, INFINITE);
674 LeaveCriticalSection(&ChildProcessRunningLock);
675
676 /* Use the exit code of the last process in the pipeline */
677 GetExitCodeProcess(hProcess[nProcesses - 1], &dwExitCode);
678 nErrorLevel = (INT)dwExitCode;
679
680 while (--nProcesses >= 0)
681 CloseHandle(hProcess[nProcesses]);
682 return;
683
684 failed:
685 if (hInput)
686 CloseHandle(hInput);
687 while (--nProcesses >= 0)
688 {
689 TerminateProcess(hProcess[nProcesses], 0);
690 CloseHandle(hProcess[nProcesses]);
691 }
692 SetStdHandle(STD_INPUT_HANDLE, hOldConIn);
693 SetStdHandle(STD_OUTPUT_HANDLE, hOldConOut);
694 #endif
695 }
696
697 INT
698 ExecuteCommand(PARSED_COMMAND *Cmd)
699 {
700 PARSED_COMMAND *Sub;
701 LPTSTR First, Rest;
702 INT Ret = 0;
703
704 if (!PerformRedirection(Cmd->Redirections))
705 return 1;
706
707 switch (Cmd->Type)
708 {
709 case C_COMMAND:
710 Ret = 1;
711 First = DoDelayedExpansion(Cmd->Command.First);
712 if (First)
713 {
714 Rest = DoDelayedExpansion(Cmd->Command.Rest);
715 if (Rest)
716 {
717 Ret = DoCommand(First, Rest, Cmd);
718 cmd_free(Rest);
719 }
720 cmd_free(First);
721 }
722 break;
723 case C_QUIET:
724 case C_BLOCK:
725 case C_MULTI:
726 for (Sub = Cmd->Subcommands; Sub; Sub = Sub->Next)
727 Ret = ExecuteCommand(Sub);
728 break;
729 case C_IFFAILURE:
730 Sub = Cmd->Subcommands;
731 Ret = ExecuteCommand(Sub);
732 if (Ret != 0)
733 {
734 nErrorLevel = Ret;
735 Ret = ExecuteCommand(Sub->Next);
736 }
737 break;
738 case C_IFSUCCESS:
739 Sub = Cmd->Subcommands;
740 Ret = ExecuteCommand(Sub);
741 if (Ret == 0)
742 Ret = ExecuteCommand(Sub->Next);
743 break;
744 case C_PIPE:
745 ExecutePipeline(Cmd);
746 break;
747 case C_IF:
748 Ret = ExecuteIf(Cmd);
749 break;
750 case C_FOR:
751 Ret = ExecuteFor(Cmd);
752 break;
753 }
754
755 UndoRedirection(Cmd->Redirections, NULL);
756 return Ret;
757 }
758
759 LPTSTR
760 GetEnvVar(LPCTSTR varName)
761 {
762 static LPTSTR ret = NULL;
763 UINT size;
764
765 cmd_free(ret);
766 ret = NULL;
767 size = GetEnvironmentVariable(varName, NULL, 0);
768 if (size > 0)
769 {
770 ret = cmd_alloc(size * sizeof(TCHAR));
771 if (ret != NULL)
772 GetEnvironmentVariable(varName, ret, size + 1);
773 }
774 return ret;
775 }
776
777 LPCTSTR
778 GetEnvVarOrSpecial(LPCTSTR varName)
779 {
780 static TCHAR ret[MAX_PATH];
781
782 LPTSTR var = GetEnvVar(varName);
783 if (var)
784 return var;
785
786 /* env var doesn't exist, look for a "special" one */
787 /* %CD% */
788 if (_tcsicmp(varName,_T("cd")) ==0)
789 {
790 GetCurrentDirectory(MAX_PATH, ret);
791 return ret;
792 }
793 /* %TIME% */
794 else if (_tcsicmp(varName,_T("time")) ==0)
795 {
796 return GetTimeString();
797 }
798 /* %DATE% */
799 else if (_tcsicmp(varName,_T("date")) ==0)
800 {
801 return GetDateString();
802 }
803
804 /* %RANDOM% */
805 else if (_tcsicmp(varName,_T("random")) ==0)
806 {
807 /* Get random number */
808 _itot(rand(),ret,10);
809 return ret;
810 }
811
812 /* %CMDCMDLINE% */
813 else if (_tcsicmp(varName,_T("cmdcmdline")) ==0)
814 {
815 return GetCommandLine();
816 }
817
818 /* %CMDEXTVERSION% */
819 else if (_tcsicmp(varName,_T("cmdextversion")) ==0)
820 {
821 /* Set version number to 2 */
822 _itot(2,ret,10);
823 return ret;
824 }
825
826 /* %ERRORLEVEL% */
827 else if (_tcsicmp(varName,_T("errorlevel")) ==0)
828 {
829 _itot(nErrorLevel,ret,10);
830 return ret;
831 }
832
833 return NULL;
834 }
835
836 /* Handle the %~var syntax */
837 static LPTSTR
838 GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, BOOL *))
839 {
840 static const TCHAR ModifierTable[] = _T("dpnxfsatz");
841 enum {
842 M_DRIVE = 1, /* D: drive letter */
843 M_PATH = 2, /* P: path */
844 M_NAME = 4, /* N: filename */
845 M_EXT = 8, /* X: extension */
846 M_FULL = 16, /* F: full path (drive+path+name+ext) */
847 M_SHORT = 32, /* S: full path (drive+path+name+ext), use short names */
848 M_ATTR = 64, /* A: attributes */
849 M_TIME = 128, /* T: modification time */
850 M_SIZE = 256, /* Z: file size */
851 } Modifiers = 0;
852
853 TCHAR *Format, *FormatEnd;
854 TCHAR *PathVarName = NULL;
855 LPTSTR Variable;
856 TCHAR *VarEnd;
857 BOOL VariableIsParam0;
858 TCHAR FullPath[MAX_PATH];
859 TCHAR FixedPath[MAX_PATH], *Filename, *Extension;
860 HANDLE hFind;
861 WIN32_FIND_DATA w32fd;
862 TCHAR *In, *Out;
863
864 static TCHAR Result[CMDLINE_LENGTH];
865
866 /* There is ambiguity between modifier characters and FOR variables;
867 * the rule that cmd uses is to pick the longest possible match.
868 * For example, if there is a %n variable, then out of %~anxnd,
869 * %~anxn will be substituted rather than just %~an. */
870
871 /* First, go through as many modifier characters as possible */
872 FormatEnd = Format = *pFormat;
873 while (*FormatEnd && _tcschr(ModifierTable, _totlower(*FormatEnd)))
874 FormatEnd++;
875
876 if (*FormatEnd == _T('$'))
877 {
878 /* $PATH: syntax */
879 PathVarName = FormatEnd + 1;
880 FormatEnd = _tcschr(PathVarName, _T(':'));
881 if (!FormatEnd)
882 return NULL;
883
884 /* Must be immediately followed by the variable */
885 Variable = GetVar(*++FormatEnd, &VariableIsParam0);
886 if (!Variable)
887 return NULL;
888 }
889 else
890 {
891 /* Backtrack if necessary to get a variable name match */
892 while (!(Variable = GetVar(*FormatEnd, &VariableIsParam0)))
893 {
894 if (FormatEnd == Format)
895 return NULL;
896 FormatEnd--;
897 }
898 }
899
900 for (; Format < FormatEnd && *Format != _T('$'); Format++)
901 Modifiers |= 1 << (_tcschr(ModifierTable, _totlower(*Format)) - ModifierTable);
902
903 *pFormat = FormatEnd + 1;
904
905 /* Exclude the leading and trailing quotes */
906 VarEnd = &Variable[_tcslen(Variable)];
907 if (*Variable == _T('"'))
908 {
909 Variable++;
910 if (VarEnd > Variable && VarEnd[-1] == _T('"'))
911 VarEnd--;
912 }
913
914 if ((char *)VarEnd - (char *)Variable >= sizeof Result)
915 return _T("");
916 memcpy(Result, Variable, (char *)VarEnd - (char *)Variable);
917 Result[VarEnd - Variable] = _T('\0');
918
919 if (PathVarName)
920 {
921 /* $PATH: syntax - search the directories listed in the
922 * specified environment variable for the file */
923 LPTSTR PathVar;
924 FormatEnd[-1] = _T('\0');
925 PathVar = GetEnvVar(PathVarName);
926 FormatEnd[-1] = _T(':');
927 if (!PathVar ||
928 !SearchPath(PathVar, Result, NULL, MAX_PATH, FullPath, NULL))
929 {
930 return _T("");
931 }
932 }
933 else if (Modifiers == 0)
934 {
935 /* For plain %~var with no modifiers, just return the variable without quotes */
936 return Result;
937 }
938 else if (VariableIsParam0)
939 {
940 /* Special case: If the variable is %0 and modifier characters are present,
941 * use the batch file's path (which includes the .bat/.cmd extension)
942 * rather than the actual %0 variable (which might not). */
943 _tcscpy(FullPath, bc->BatchFilePath);
944 }
945 else
946 {
947 /* Convert the variable, now without quotes, to a full path */
948 if (!GetFullPathName(Result, MAX_PATH, FullPath, NULL))
949 return _T("");
950 }
951
952 /* Next step is to change the path to fix letter case (e.g.
953 * C:\ReAcToS -> C:\ReactOS) and, if requested with the S modifier,
954 * replace long filenames with short. */
955
956 In = FullPath;
957 Out = FixedPath;
958
959 /* Copy drive letter */
960 *Out++ = *In++;
961 *Out++ = *In++;
962 *Out++ = *In++;
963 /* Loop over each \-separated component in the path */
964 do {
965 TCHAR *Next = _tcschr(In, _T('\\'));
966 if (Next)
967 *Next++ = _T('\0');
968 /* Use FindFirstFile to get the correct name */
969 if (Out + _tcslen(In) + 1 >= &FixedPath[MAX_PATH])
970 return _T("");
971 _tcscpy(Out, In);
972 hFind = FindFirstFile(FixedPath, &w32fd);
973 /* If it doesn't exist, just leave the name as it was given */
974 if (hFind != INVALID_HANDLE_VALUE)
975 {
976 LPTSTR FixedComponent = w32fd.cFileName;
977 if (*w32fd.cAlternateFileName &&
978 ((Modifiers & M_SHORT) || !_tcsicmp(In, w32fd.cAlternateFileName)))
979 {
980 FixedComponent = w32fd.cAlternateFileName;
981 }
982 FindClose(hFind);
983
984 if (Out + _tcslen(FixedComponent) + 1 >= &FixedPath[MAX_PATH])
985 return _T("");
986 _tcscpy(Out, FixedComponent);
987 }
988 Filename = Out;
989 Out += _tcslen(Out);
990 *Out++ = _T('\\');
991
992 In = Next;
993 } while (In != NULL);
994 Out[-1] = _T('\0');
995
996 /* Build the result string. Start with attributes, modification time, and
997 * file size. If the file didn't exist, these fields will all be empty. */
998 Out = Result;
999 if (hFind != INVALID_HANDLE_VALUE)
1000 {
1001 if (Modifiers & M_ATTR)
1002 {
1003 static const struct {
1004 TCHAR Character;
1005 WORD Value;
1006 } *Attrib, Table[] = {
1007 { _T('d'), FILE_ATTRIBUTE_DIRECTORY },
1008 { _T('r'), FILE_ATTRIBUTE_READONLY },
1009 { _T('a'), FILE_ATTRIBUTE_ARCHIVE },
1010 { _T('h'), FILE_ATTRIBUTE_HIDDEN },
1011 { _T('s'), FILE_ATTRIBUTE_SYSTEM },
1012 { _T('c'), FILE_ATTRIBUTE_COMPRESSED },
1013 { _T('o'), FILE_ATTRIBUTE_OFFLINE },
1014 { _T('t'), FILE_ATTRIBUTE_TEMPORARY },
1015 { _T('l'), FILE_ATTRIBUTE_REPARSE_POINT },
1016 };
1017 for (Attrib = Table; Attrib != &Table[9]; Attrib++)
1018 {
1019 *Out++ = w32fd.dwFileAttributes & Attrib->Value
1020 ? Attrib->Character
1021 : _T('-');
1022 }
1023 *Out++ = _T(' ');
1024 }
1025 if (Modifiers & M_TIME)
1026 {
1027 FILETIME ft;
1028 SYSTEMTIME st;
1029 FileTimeToLocalFileTime(&w32fd.ftLastWriteTime, &ft);
1030 FileTimeToSystemTime(&ft, &st);
1031
1032 Out += FormatDate(Out, &st, TRUE);
1033 *Out++ = _T(' ');
1034 Out += FormatTime(Out, &st);
1035 *Out++ = _T(' ');
1036 }
1037 if (Modifiers & M_SIZE)
1038 {
1039 ULARGE_INTEGER Size;
1040 Size.LowPart = w32fd.nFileSizeLow;
1041 Size.HighPart = w32fd.nFileSizeHigh;
1042 Out += _stprintf(Out, _T("%I64u "), Size.QuadPart);
1043 }
1044 }
1045
1046 /* When using the path-searching syntax or the S modifier,
1047 * at least part of the file path is always included.
1048 * If none of the DPNX modifiers are present, include the full path */
1049 if (PathVarName || (Modifiers & M_SHORT))
1050 if ((Modifiers & (M_DRIVE | M_PATH | M_NAME | M_EXT)) == 0)
1051 Modifiers |= M_FULL;
1052
1053 /* Now add the requested parts of the name.
1054 * With the F modifier, add all parts to form the full path. */
1055 Extension = _tcsrchr(Filename, _T('.'));
1056 if (Modifiers & (M_DRIVE | M_FULL))
1057 {
1058 *Out++ = FixedPath[0];
1059 *Out++ = FixedPath[1];
1060 }
1061 if (Modifiers & (M_PATH | M_FULL))
1062 {
1063 memcpy(Out, &FixedPath[2], (char *)Filename - (char *)&FixedPath[2]);
1064 Out += Filename - &FixedPath[2];
1065 }
1066 if (Modifiers & (M_NAME | M_FULL))
1067 {
1068 while (*Filename && Filename != Extension)
1069 *Out++ = *Filename++;
1070 }
1071 if (Modifiers & (M_EXT | M_FULL))
1072 {
1073 if (Extension)
1074 Out = _stpcpy(Out, Extension);
1075 }
1076
1077 /* Trim trailing space which otherwise would appear as a
1078 * result of using the A/T/Z modifiers but no others. */
1079 while (Out != &Result[0] && Out[-1] == _T(' '))
1080 Out--;
1081 *Out = _T('\0');
1082
1083 return Result;
1084 }
1085
1086 LPCTSTR
1087 GetBatchVar(TCHAR *varName, UINT *varNameLen)
1088 {
1089 LPCTSTR ret;
1090 TCHAR *varNameEnd;
1091 BOOL dummy;
1092
1093 *varNameLen = 1;
1094
1095 switch ( *varName )
1096 {
1097 case _T('~'):
1098 varNameEnd = varName + 1;
1099 ret = GetEnhancedVar(&varNameEnd, FindArg);
1100 if (!ret)
1101 {
1102 error_syntax(varName);
1103 return NULL;
1104 }
1105 *varNameLen = varNameEnd - varName;
1106 return ret;
1107 case _T('0'):
1108 case _T('1'):
1109 case _T('2'):
1110 case _T('3'):
1111 case _T('4'):
1112 case _T('5'):
1113 case _T('6'):
1114 case _T('7'):
1115 case _T('8'):
1116 case _T('9'):
1117 return FindArg(*varName, &dummy);
1118
1119 case _T('*'):
1120 //
1121 // Copy over the raw params(not including the batch file name
1122 //
1123 return bc->raw_params;
1124
1125 case _T('%'):
1126 return _T("%");
1127 }
1128 return NULL;
1129 }
1130
1131 BOOL
1132 SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim)
1133 {
1134 #define APPEND(From, Length) { \
1135 if (Dest + (Length) > DestEnd) \
1136 goto too_long; \
1137 memcpy(Dest, From, (Length) * sizeof(TCHAR)); \
1138 Dest += Length; }
1139 #define APPEND1(Char) { \
1140 if (Dest >= DestEnd) \
1141 goto too_long; \
1142 *Dest++ = Char; }
1143
1144 TCHAR *DestEnd = Dest + CMDLINE_LENGTH - 1;
1145 const TCHAR *Var;
1146 int VarLength;
1147 TCHAR *SubstStart;
1148 TCHAR EndChr;
1149 while (*Src)
1150 {
1151 if (*Src != Delim)
1152 {
1153 APPEND1(*Src++)
1154 continue;
1155 }
1156
1157 Src++;
1158 if (bc && Delim == _T('%'))
1159 {
1160 UINT NameLen;
1161 Var = GetBatchVar(Src, &NameLen);
1162 if (Var != NULL)
1163 {
1164 VarLength = _tcslen(Var);
1165 APPEND(Var, VarLength)
1166 Src += NameLen;
1167 continue;
1168 }
1169 }
1170
1171 /* Find the end of the variable name. A colon (:) will usually
1172 * end the name and begin the optional modifier, but not if it
1173 * is immediately followed by the delimiter (%VAR:%). */
1174 SubstStart = Src;
1175 while (*Src != Delim && !(*Src == _T(':') && Src[1] != Delim))
1176 {
1177 if (!*Src)
1178 goto bad_subst;
1179 Src++;
1180 }
1181
1182 EndChr = *Src;
1183 *Src = _T('\0');
1184 Var = GetEnvVarOrSpecial(SubstStart);
1185 *Src++ = EndChr;
1186 if (Var == NULL)
1187 {
1188 /* In a batch file, %NONEXISTENT% "expands" to an empty string */
1189 if (bc)
1190 continue;
1191 goto bad_subst;
1192 }
1193 VarLength = _tcslen(Var);
1194
1195 if (EndChr == Delim)
1196 {
1197 /* %VAR% - use as-is */
1198 APPEND(Var, VarLength)
1199 }
1200 else if (*Src == _T('~'))
1201 {
1202 /* %VAR:~[start][,length]% - substring
1203 * Negative values are offsets from the end */
1204 int Start = _tcstol(Src + 1, &Src, 0);
1205 int End = VarLength;
1206 if (Start < 0)
1207 Start += VarLength;
1208 Start = max(Start, 0);
1209 Start = min(Start, VarLength);
1210 if (*Src == _T(','))
1211 {
1212 End = _tcstol(Src + 1, &Src, 0);
1213 End += (End < 0) ? VarLength : Start;
1214 End = max(End, Start);
1215 End = min(End, VarLength);
1216 }
1217 if (*Src++ != Delim)
1218 goto bad_subst;
1219 APPEND(&Var[Start], End - Start);
1220 }
1221 else
1222 {
1223 /* %VAR:old=new% - replace all occurrences of old with new
1224 * %VAR:*old=new% - replace first occurrence only,
1225 * and remove everything before it */
1226 TCHAR *Old, *New;
1227 DWORD OldLength, NewLength;
1228 BOOL Star = FALSE;
1229 int LastMatch = 0, i = 0;
1230
1231 if (*Src == _T('*'))
1232 {
1233 Star = TRUE;
1234 Src++;
1235 }
1236
1237 /* the string to replace may contain the delimiter */
1238 Src = _tcschr(Old = Src, _T('='));
1239 if (Src == NULL)
1240 goto bad_subst;
1241 OldLength = Src++ - Old;
1242 if (OldLength == 0)
1243 goto bad_subst;
1244
1245 Src = _tcschr(New = Src, Delim);
1246 if (Src == NULL)
1247 goto bad_subst;
1248 NewLength = Src++ - New;
1249
1250 while (i < VarLength)
1251 {
1252 if (_tcsnicmp(&Var[i], Old, OldLength) == 0)
1253 {
1254 if (!Star)
1255 APPEND(&Var[LastMatch], i - LastMatch)
1256 APPEND(New, NewLength)
1257 i += OldLength;
1258 LastMatch = i;
1259 if (Star)
1260 break;
1261 continue;
1262 }
1263 i++;
1264 }
1265 APPEND(&Var[LastMatch], VarLength - LastMatch)
1266 }
1267 continue;
1268
1269 bad_subst:
1270 Src = SubstStart;
1271 if (!bc)
1272 APPEND1(Delim)
1273 }
1274 *Dest = _T('\0');
1275 return TRUE;
1276 too_long:
1277 ConOutResPrintf(STRING_ALIAS_ERROR);
1278 nErrorLevel = 9023;
1279 return FALSE;
1280 #undef APPEND
1281 #undef APPEND1
1282 }
1283
1284 /* Search the list of FOR contexts for a variable */
1285 static LPTSTR FindForVar(TCHAR Var, BOOL *IsParam0)
1286 {
1287 FOR_CONTEXT *Ctx;
1288 *IsParam0 = FALSE;
1289 for (Ctx = fc; Ctx != NULL; Ctx = Ctx->prev)
1290 if ((UINT)(Var - Ctx->firstvar) < Ctx->varcount)
1291 return Ctx->values[Var - Ctx->firstvar];
1292 return NULL;
1293 }
1294
1295 BOOL
1296 SubstituteForVars(TCHAR *Src, TCHAR *Dest)
1297 {
1298 TCHAR *DestEnd = &Dest[CMDLINE_LENGTH - 1];
1299 while (*Src)
1300 {
1301 if (Src[0] == _T('%'))
1302 {
1303 BOOL Dummy;
1304 LPTSTR End = &Src[2];
1305 LPTSTR Value = NULL;
1306
1307 if (Src[1] == _T('~'))
1308 Value = GetEnhancedVar(&End, FindForVar);
1309
1310 if (!Value)
1311 Value = FindForVar(Src[1], &Dummy);
1312
1313 if (Value)
1314 {
1315 if (Dest + _tcslen(Value) > DestEnd)
1316 return FALSE;
1317 Dest = _stpcpy(Dest, Value);
1318 Src = End;
1319 continue;
1320 }
1321 }
1322 /* Not a variable; just copy the character */
1323 if (Dest >= DestEnd)
1324 return FALSE;
1325 *Dest++ = *Src++;
1326 }
1327 *Dest = _T('\0');
1328 return TRUE;
1329 }
1330
1331 LPTSTR
1332 DoDelayedExpansion(LPTSTR Line)
1333 {
1334 TCHAR Buf1[CMDLINE_LENGTH];
1335 TCHAR Buf2[CMDLINE_LENGTH];
1336
1337 /* First, substitute FOR variables */
1338 if (!SubstituteForVars(Line, Buf1))
1339 return NULL;
1340
1341 if (!bDelayedExpansion || !_tcschr(Buf1, _T('!')))
1342 return cmd_dup(Buf1);
1343
1344 /* FIXME: Delayed substitutions actually aren't quite the same as
1345 * immediate substitutions. In particular, it's possible to escape
1346 * the exclamation point using ^. */
1347 if (!SubstituteVars(Buf1, Buf2, _T('!')))
1348 return NULL;
1349 return cmd_dup(Buf2);
1350 }
1351
1352
1353 /*
1354 * do the prompt/input/process loop
1355 *
1356 */
1357
1358 BOOL
1359 ReadLine (TCHAR *commandline, BOOL bMore)
1360 {
1361 TCHAR readline[CMDLINE_LENGTH];
1362 LPTSTR ip;
1363
1364 /* if no batch input then... */
1365 if (bc == NULL)
1366 {
1367 if (bMore)
1368 {
1369 ConOutResPrintf(STRING_MORE);
1370 }
1371 else
1372 {
1373 /* JPP 19980807 - if echo off, don't print prompt */
1374 if (bEcho)
1375 {
1376 if (!bIgnoreEcho)
1377 ConOutChar('\n');
1378 PrintPrompt();
1379 }
1380 }
1381
1382 if (!ReadCommand(readline, CMDLINE_LENGTH - 1))
1383 {
1384 bExit = TRUE;
1385 return FALSE;
1386 }
1387
1388 if (CheckCtrlBreak(BREAK_INPUT))
1389 {
1390 ConOutPuts(_T("\n"));
1391 return FALSE;
1392 }
1393 ip = readline;
1394 }
1395 else
1396 {
1397 ip = ReadBatchLine();
1398 if (!ip)
1399 return FALSE;
1400 }
1401
1402 return SubstituteVars(ip, commandline, _T('%'));
1403 }
1404
1405 static VOID
1406 ProcessInput()
1407 {
1408 PARSED_COMMAND *Cmd;
1409
1410 while (!bCanExit || !bExit)
1411 {
1412 Cmd = ParseCommand(NULL);
1413 if (!Cmd)
1414 continue;
1415
1416 ExecuteCommand(Cmd);
1417 FreeCommand(Cmd);
1418 }
1419 }
1420
1421
1422 /*
1423 * control-break handler.
1424 */
1425 BOOL WINAPI BreakHandler (DWORD dwCtrlType)
1426 {
1427
1428 DWORD dwWritten;
1429 INPUT_RECORD rec;
1430 static BOOL SelfGenerated = FALSE;
1431
1432 if ((dwCtrlType != CTRL_C_EVENT) &&
1433 (dwCtrlType != CTRL_BREAK_EVENT))
1434 {
1435 return FALSE;
1436 }
1437 else
1438 {
1439 if(SelfGenerated)
1440 {
1441 SelfGenerated = FALSE;
1442 return TRUE;
1443 }
1444 }
1445
1446 if (!TryEnterCriticalSection(&ChildProcessRunningLock))
1447 {
1448 SelfGenerated = TRUE;
1449 GenerateConsoleCtrlEvent (dwCtrlType, 0);
1450 return TRUE;
1451 }
1452 else
1453 {
1454 LeaveCriticalSection(&ChildProcessRunningLock);
1455 }
1456
1457 rec.EventType = KEY_EVENT;
1458 rec.Event.KeyEvent.bKeyDown = TRUE;
1459 rec.Event.KeyEvent.wRepeatCount = 1;
1460 rec.Event.KeyEvent.wVirtualKeyCode = _T('C');
1461 rec.Event.KeyEvent.wVirtualScanCode = _T('C') - 35;
1462 rec.Event.KeyEvent.uChar.AsciiChar = _T('C');
1463 rec.Event.KeyEvent.uChar.UnicodeChar = _T('C');
1464 rec.Event.KeyEvent.dwControlKeyState = RIGHT_CTRL_PRESSED;
1465
1466 WriteConsoleInput(
1467 hIn,
1468 &rec,
1469 1,
1470 &dwWritten);
1471
1472 bCtrlBreak = TRUE;
1473 /* FIXME: Handle batch files */
1474
1475 //ConOutPrintf(_T("^C"));
1476
1477
1478 return TRUE;
1479 }
1480
1481
1482 VOID AddBreakHandler (VOID)
1483 {
1484 SetConsoleCtrlHandler ((PHANDLER_ROUTINE)BreakHandler, TRUE);
1485 }
1486
1487
1488 VOID RemoveBreakHandler (VOID)
1489 {
1490 SetConsoleCtrlHandler ((PHANDLER_ROUTINE)BreakHandler, FALSE);
1491 }
1492
1493
1494 /*
1495 * show commands and options that are available.
1496 *
1497 */
1498 #if 0
1499 static VOID
1500 ShowCommands (VOID)
1501 {
1502 /* print command list */
1503 ConOutResPuts(STRING_CMD_HELP1);
1504 PrintCommandList();
1505
1506 /* print feature list */
1507 ConOutResPuts(STRING_CMD_HELP2);
1508
1509 #ifdef FEATURE_ALIASES
1510 ConOutResPuts(STRING_CMD_HELP3);
1511 #endif
1512 #ifdef FEATURE_HISTORY
1513 ConOutResPuts(STRING_CMD_HELP4);
1514 #endif
1515 #ifdef FEATURE_UNIX_FILENAME_COMPLETION
1516 ConOutResPuts(STRING_CMD_HELP5);
1517 #endif
1518 #ifdef FEATURE_DIRECTORY_STACK
1519 ConOutResPuts(STRING_CMD_HELP6);
1520 #endif
1521 #ifdef FEATURE_REDIRECTION
1522 ConOutResPuts(STRING_CMD_HELP7);
1523 #endif
1524 ConOutChar(_T('\n'));
1525 }
1526 #endif
1527
1528 static VOID
1529 ExecuteAutoRunFile(HKEY hkeyRoot)
1530 {
1531 TCHAR autorun[2048];
1532 DWORD len = sizeof autorun;
1533 HKEY hkey;
1534
1535 if (RegOpenKeyEx(hkeyRoot,
1536 _T("SOFTWARE\\Microsoft\\Command Processor"),
1537 0,
1538 KEY_READ,
1539 &hkey ) == ERROR_SUCCESS)
1540 {
1541 if(RegQueryValueEx(hkey,
1542 _T("AutoRun"),
1543 0,
1544 0,
1545 (LPBYTE)autorun,
1546 &len) == ERROR_SUCCESS)
1547 {
1548 if (*autorun)
1549 ParseCommandLine(autorun);
1550 }
1551 RegCloseKey(hkey);
1552 }
1553 }
1554
1555 /* Get the command that comes after a /C or /K switch */
1556 static VOID
1557 GetCmdLineCommand(TCHAR *commandline, TCHAR *ptr, BOOL AlwaysStrip)
1558 {
1559 TCHAR *LastQuote;
1560
1561 while (_istspace(*ptr))
1562 ptr++;
1563
1564 /* Remove leading quote, find final quote */
1565 if (*ptr == _T('"') &&
1566 (LastQuote = _tcsrchr(++ptr, _T('"'))) != NULL)
1567 {
1568 TCHAR *Space;
1569 /* Under certain circumstances, all quotes are preserved.
1570 * CMD /? documents these conditions as follows:
1571 * 1. No /S switch
1572 * 2. Exactly two quotes
1573 * 3. No "special characters" between the quotes
1574 * (CMD /? says &<>()@^| but parentheses did not
1575 * trigger this rule when I tested them.)
1576 * 4. Whitespace exists between the quotes
1577 * 5. Enclosed string is an executable filename
1578 */
1579 *LastQuote = _T('\0');
1580 for (Space = ptr + 1; Space < LastQuote; Space++)
1581 {
1582 if (_istspace(*Space)) /* Rule 4 */
1583 {
1584 if (!AlwaysStrip && /* Rule 1 */
1585 !_tcspbrk(ptr, _T("\"&<>@^|")) && /* Rules 2, 3 */
1586 SearchForExecutable(ptr, commandline)) /* Rule 5 */
1587 {
1588 /* All conditions met: preserve both the quotes */
1589 *LastQuote = _T('"');
1590 _tcscpy(commandline, ptr - 1);
1591 return;
1592 }
1593 break;
1594 }
1595 }
1596
1597 /* The conditions were not met: remove both the
1598 * leading quote and the last quote */
1599 _tcscpy(commandline, ptr);
1600 _tcscpy(&commandline[LastQuote - ptr], LastQuote + 1);
1601 return;
1602 }
1603
1604 /* No quotes; just copy */
1605 _tcscpy(commandline, ptr);
1606 }
1607
1608 /*
1609 * set up global initializations and process parameters
1610 */
1611 static VOID
1612 Initialize()
1613 {
1614 HMODULE NtDllModule;
1615 TCHAR commandline[CMDLINE_LENGTH];
1616 TCHAR ModuleName[_MAX_PATH + 1];
1617 TCHAR lpBuffer[2];
1618 INT nExitCode;
1619
1620 //INT len;
1621 TCHAR *ptr, *cmdLine, option = 0;
1622 BOOL AlwaysStrip = FALSE;
1623 BOOL AutoRun = TRUE;
1624
1625 /* get version information */
1626 osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
1627 GetVersionEx (&osvi);
1628
1629 /* Some people like to run ReactOS cmd.exe on Win98, it helps in the
1630 * build process. So don't link implicitly against ntdll.dll, load it
1631 * dynamically instead */
1632 NtDllModule = GetModuleHandle(TEXT("ntdll.dll"));
1633 if (NtDllModule != NULL)
1634 {
1635 NtQueryInformationProcessPtr = (NtQueryInformationProcessProc)GetProcAddress(NtDllModule, "NtQueryInformationProcess");
1636 NtReadVirtualMemoryPtr = (NtReadVirtualMemoryProc)GetProcAddress(NtDllModule, "NtReadVirtualMemory");
1637 }
1638
1639 InitLocale ();
1640
1641 /* get default input and output console handles */
1642 hOut = GetStdHandle (STD_OUTPUT_HANDLE);
1643 hIn = GetStdHandle (STD_INPUT_HANDLE);
1644
1645 /* Set EnvironmentVariable PROMPT if it does not exists any env value.
1646 for you can change the EnvirommentVariable for prompt before cmd start
1647 this patch are not 100% right, if it does not exists a PROMPT value cmd should use
1648 $P$G as defualt not set EnvirommentVariable PROMPT to $P$G if it does not exists */
1649 if (GetEnvironmentVariable(_T("PROMPT"),lpBuffer, sizeof(lpBuffer) / sizeof(lpBuffer[0])) == 0)
1650 SetEnvironmentVariable (_T("PROMPT"), _T("$P$G"));
1651
1652 #ifdef FEATURE_DIR_STACK
1653 /* initialize directory stack */
1654 InitDirectoryStack ();
1655 #endif
1656
1657 #ifdef FEATURE_HISTORY
1658 /*initialize history*/
1659 InitHistory();
1660 #endif
1661
1662 /* Set COMSPEC environment variable */
1663 if (0 != GetModuleFileName (NULL, ModuleName, _MAX_PATH + 1))
1664 {
1665 ModuleName[_MAX_PATH] = _T('\0');
1666 SetEnvironmentVariable (_T("COMSPEC"), ModuleName);
1667 }
1668
1669 /* add ctrl break handler */
1670 AddBreakHandler ();
1671
1672
1673 SetConsoleMode (hIn, ENABLE_PROCESSED_INPUT);
1674
1675 cmdLine = GetCommandLine();
1676 TRACE ("[command args: %s]\n", debugstr_aw(cmdLine));
1677
1678 for (ptr = cmdLine; *ptr; ptr++)
1679 {
1680 if (*ptr == _T('/'))
1681 {
1682 option = _totupper(ptr[1]);
1683 if (option == _T('?'))
1684 {
1685 ConOutResPaging(TRUE,STRING_CMD_HELP8);
1686 nErrorLevel = 1;
1687 bExit = TRUE;
1688 return;
1689 }
1690 else if (option == _T('P'))
1691 {
1692 if (!IsExistingFile (_T("\\autoexec.bat")))
1693 {
1694 #ifdef INCLUDE_CMD_DATE
1695 cmd_date (_T(""));
1696 #endif
1697 #ifdef INCLUDE_CMD_TIME
1698 cmd_time (_T(""));
1699 #endif
1700 }
1701 else
1702 {
1703 ParseCommandLine (_T("\\autoexec.bat"));
1704 }
1705 bCanExit = FALSE;
1706 }
1707 else if (option == _T('A'))
1708 {
1709 bUnicodeOutput = FALSE;
1710 }
1711 else if (option == _T('C') || option == _T('K') || option == _T('R'))
1712 {
1713 /* Remainder of command line is a command to be run */
1714 break;
1715 }
1716 else if (option == _T('D'))
1717 {
1718 AutoRun = FALSE;
1719 }
1720 else if (option == _T('Q'))
1721 {
1722 bDisableBatchEcho = TRUE;
1723 }
1724 else if (option == _T('S'))
1725 {
1726 AlwaysStrip = TRUE;
1727 }
1728 #ifdef INCLUDE_CMD_COLOR
1729 else if (!_tcsnicmp(ptr, _T("/T:"), 3))
1730 {
1731 /* process /t (color) argument */
1732 wDefColor = (WORD)_tcstoul(&ptr[3], &ptr, 16);
1733 SetScreenColor(wDefColor, TRUE);
1734 }
1735 #endif
1736 else if (option == _T('U'))
1737 {
1738 bUnicodeOutput = TRUE;
1739 }
1740 else if (option == _T('V'))
1741 {
1742 bDelayedExpansion = _tcsnicmp(&ptr[2], _T(":OFF"), 4);
1743 }
1744 }
1745 }
1746
1747 if (!*ptr)
1748 {
1749 /* If neither /C or /K was given, display a simple version string */
1750 ConOutResPrintf(STRING_REACTOS_VERSION,
1751 _T(KERNEL_RELEASE_STR),
1752 _T(KERNEL_VERSION_BUILD_STR));
1753 ConOutPuts(_T("(C) Copyright 1998-") _T(COPYRIGHT_YEAR) _T(" ReactOS Team."));
1754 }
1755
1756 if (AutoRun)
1757 {
1758 ExecuteAutoRunFile(HKEY_LOCAL_MACHINE);
1759 ExecuteAutoRunFile(HKEY_CURRENT_USER);
1760 }
1761
1762 if (*ptr)
1763 {
1764 /* Do the /C or /K command */
1765 GetCmdLineCommand(commandline, &ptr[2], AlwaysStrip);
1766 nExitCode = ParseCommandLine(commandline);
1767 if (option != _T('K'))
1768 {
1769 nErrorLevel = nExitCode;
1770 bExit = TRUE;
1771 }
1772 }
1773 }
1774
1775
1776 static VOID Cleanup()
1777 {
1778 /* run cmdexit.bat */
1779 if (IsExistingFile (_T("cmdexit.bat")))
1780 {
1781 ConErrResPuts(STRING_CMD_ERROR5);
1782
1783 ParseCommandLine (_T("cmdexit.bat"));
1784 }
1785 else if (IsExistingFile (_T("\\cmdexit.bat")))
1786 {
1787 ConErrResPuts (STRING_CMD_ERROR5);
1788 ParseCommandLine (_T("\\cmdexit.bat"));
1789 }
1790
1791 #ifdef FEATURE_DIECTORY_STACK
1792 /* destroy directory stack */
1793 DestroyDirectoryStack ();
1794 #endif
1795
1796 #ifdef FEATURE_HISTORY
1797 CleanHistory();
1798 #endif
1799
1800 /* free GetEnvVar's buffer */
1801 GetEnvVar(NULL);
1802
1803 /* remove ctrl break handler */
1804 RemoveBreakHandler ();
1805 SetConsoleMode( GetStdHandle( STD_INPUT_HANDLE ),
1806 ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT );
1807 DeleteCriticalSection(&ChildProcessRunningLock);
1808 }
1809
1810 /*
1811 * main function
1812 */
1813 int cmd_main (int argc, const TCHAR *argv[])
1814 {
1815 HANDLE hConsole;
1816 TCHAR startPath[MAX_PATH];
1817 CONSOLE_SCREEN_BUFFER_INFO Info;
1818
1819 InitializeCriticalSection(&ChildProcessRunningLock);
1820 lpOriginalEnvironment = DuplicateEnvironment();
1821
1822 GetCurrentDirectory(MAX_PATH,startPath);
1823 _tchdir(startPath);
1824
1825 SetFileApisToOEM();
1826 InputCodePage= 0;
1827 OutputCodePage = 0;
1828
1829 hConsole = CreateFile(_T("CONOUT$"), GENERIC_READ|GENERIC_WRITE,
1830 FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
1831 OPEN_EXISTING, 0, NULL);
1832 if (hConsole != INVALID_HANDLE_VALUE)
1833 {
1834 if (!GetConsoleScreenBufferInfo(hConsole, &Info))
1835 {
1836 ConErrFormatMessage(GetLastError());
1837 return(1);
1838 }
1839 wDefColor = Info.wAttributes;
1840 CloseHandle(hConsole);
1841 }
1842
1843 InputCodePage= GetConsoleCP();
1844 OutputCodePage = GetConsoleOutputCP();
1845 CMD_ModuleHandle = GetModuleHandle(NULL);
1846
1847 /* check switches on command-line */
1848 Initialize();
1849
1850 /* call prompt routine */
1851 ProcessInput();
1852
1853 /* do the cleanup */
1854 Cleanup();
1855
1856 cmd_free(lpOriginalEnvironment);
1857
1858 cmd_exit(nErrorLevel);
1859 return(nErrorLevel);
1860 }
1861
1862 /* EOF */