Copy riched20
[reactos.git] / reactos / subsys / system / cmd / cmd.c
1 /* $Id$
2 *
3 * CMD.C - command-line interface.
4 *
5 *
6 * History:
7 *
8 * 17 Jun 1994 (Tim Norman)
9 * started.
10 *
11 * 08 Aug 1995 (Matt Rains)
12 * I have cleaned up the source code. changes now bring this source
13 * into guidelines for recommended programming practice.
14 *
15 * A added the the standard FreeDOS GNU licence test to the
16 * initialize() function.
17 *
18 * Started to replace puts() with printf(). this will help
19 * standardize output. please follow my lead.
20 *
21 * I have added some constants to help making changes easier.
22 *
23 * 15 Dec 1995 (Tim Norman)
24 * major rewrite of the code to make it more efficient and add
25 * redirection support (finally!)
26 *
27 * 06 Jan 1996 (Tim Norman)
28 * finished adding redirection support! Changed to use our own
29 * exec code (MUCH thanks to Svante Frey!!)
30 *
31 * 29 Jan 1996 (Tim Norman)
32 * added support for CHDIR, RMDIR, MKDIR, and ERASE, as per
33 * suggestion of Steffan Kaiser
34 *
35 * changed "file not found" error message to "bad command or
36 * filename" thanks to Dustin Norman for noticing that confusing
37 * message!
38 *
39 * changed the format to call internal commands (again) so that if
40 * they want to split their commands, they can do it themselves
41 * (none of the internal functions so far need that much power, anyway)
42 *
43 * 27 Aug 1996 (Tim Norman)
44 * added in support for Oliver Mueller's ALIAS command
45 *
46 * 14 Jun 1997 (Steffan Kaiser)
47 * added ctrl-break handling and error level
48 *
49 * 16 Jun 1998 (Rob Lake)
50 * Runs command.com if /P is specified in command line. Command.com
51 * also stays permanent. If /C is in the command line, starts the
52 * program next in the line.
53 *
54 * 21 Jun 1998 (Rob Lake)
55 * Fixed up /C so that arguments for the program
56 *
57 * 08-Jul-1998 (John P. Price)
58 * Now sets COMSPEC environment variable
59 * misc clean up and optimization
60 * added date and time commands
61 * changed to using spawnl instead of exec. exec does not copy the
62 * environment to the child process!
63 *
64 * 14 Jul 1998 (Hans B Pufal)
65 * Reorganised source to be more efficient and to more closely
66 * follow MS-DOS conventions. (eg %..% environment variable
67 * replacement works form command line as well as batch file.
68 *
69 * New organisation also properly support nested batch files.
70 *
71 * New command table structure is half way towards providing a
72 * system in which COMMAND will find out what internal commands
73 * are loaded
74 *
75 * 24 Jul 1998 (Hans B Pufal) [HBP_003]
76 * Fixed return value when called with /C option
77 *
78 * 27 Jul 1998 John P. Price
79 * added config.h include
80 *
81 * 28 Jul 1998 John P. Price
82 * added showcmds function to show commands and options available
83 *
84 * 07-Aug-1998 (John P Price <linux-guru@gcfl.net>)
85 * Fixed carrage return output to better match MSDOS with echo
86 * on or off. (marked with "JPP 19980708")
87 *
88 * 07-Dec-1998 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
89 * First ReactOS release.
90 * Extended length of commandline buffers to 512.
91 *
92 * 13-Dec-1998 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
93 * Added COMSPEC environment variable.
94 * Added "/t" support (color) on cmd command line.
95 *
96 * 07-Jan-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
97 * Added help text ("cmd /?").
98 *
99 * 25-Jan-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
100 * Unicode and redirection safe!
101 * Fixed redirections and piping.
102 * Piping is based on temporary files, but basic support
103 * for anonymous pipes already exists.
104 *
105 * 27-Jan-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
106 * Replaced spawnl() by CreateProcess().
107 *
108 * 22-Oct-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
109 * Added break handler.
110 *
111 * 15-Dec-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
112 * Fixed current directory
113 *
114 * 28-Dec-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
115 * Restore window title after program/batch execution
116 *
117 * 03-Feb-2001 (Eric Kohl <ekohl@rz-online.de>)
118 * Workaround because argc[0] is NULL under ReactOS
119 *
120 * 23-Feb-2001 (Carl Nettelblad <cnettel@hem.passagen.se>)
121 * %envvar% replacement conflicted with for.
122 *
123 * 30-Apr-2004 (Filip Navara <xnavara@volny.cz>)
124 * Make MakeSureDirectoryPathExistsEx unicode safe.
125 *
126 * 28-Mai-2004 (Hartmut Birr)
127 * Removed MakeSureDirectoryPathExistsEx.
128 * Use the current directory if GetTempPath fails.
129 *
130 * 12-Jul-2004 (Jens Collin <jens.collin@lakhei.com>)
131 * Added ShellExecute call when all else fails to be able to "launch" any file.
132 */
133
134 #include "precomp.h"
135
136 #ifndef NT_SUCCESS
137 #define NT_SUCCESS(StatCode) ((NTSTATUS)(StatCode) >= 0)
138 #endif
139
140 typedef NTSTATUS (STDCALL *NtQueryInformationProcessProc)(HANDLE, PROCESSINFOCLASS,
141 PVOID, ULONG, PULONG);
142 typedef NTSTATUS (STDCALL *NtReadVirtualMemoryProc)(HANDLE, PVOID, PVOID, ULONG, PULONG);
143
144 BOOL bExit = FALSE; /* indicates EXIT was typed */
145 BOOL bCanExit = TRUE; /* indicates if this shell is exitable */
146 BOOL bCtrlBreak = FALSE; /* Ctrl-Break or Ctrl-C hit */
147 BOOL bIgnoreEcho = FALSE; /* Ignore 'newline' before 'cls' */
148 INT nErrorLevel = 0; /* Errorlevel of last launched external program */
149 BOOL bChildProcessRunning = FALSE;
150 DWORD dwChildProcessId = 0;
151 OSVERSIONINFO osvi;
152 HANDLE hIn;
153 HANDLE hOut;
154 HANDLE hConsole;
155
156 static NtQueryInformationProcessProc NtQueryInformationProcessPtr;
157 static NtReadVirtualMemoryProc NtReadVirtualMemoryPtr;
158 static BOOL NtDllChecked = FALSE;
159
160 #ifdef INCLUDE_CMD_COLOR
161 WORD wColor; /* current color */
162 WORD wDefColor; /* default color */
163 #endif
164
165 /*
166 * is character a delimeter when used on first word?
167 *
168 */
169
170 static BOOL IsDelimiter (TCHAR c)
171 {
172 return (c == _T('/') || c == _T('=') || c == _T('\0') || _istspace (c));
173 }
174
175 /*
176 * Is a process a console process?
177 */
178 static BOOL IsConsoleProcess(HANDLE Process)
179 {
180 NTSTATUS Status;
181 PROCESS_BASIC_INFORMATION Info;
182 PEB ProcessPeb;
183 ULONG BytesRead;
184 HMODULE NtDllModule;
185
186 /* Some people like to run ReactOS cmd.exe on Win98, it helps in the
187 build process. So don't link implicitly against ntdll.dll, load it
188 dynamically instead */
189 if (! NtDllChecked)
190 {
191 NtDllChecked = TRUE;
192 NtDllModule = LoadLibrary(_T("ntdll.dll"));
193 if (NULL == NtDllModule)
194 {
195 /* Probably non-WinNT system. Just wait for the commands
196 to finish. */
197 NtQueryInformationProcessPtr = NULL;
198 NtReadVirtualMemoryPtr = NULL;
199 return TRUE;
200 }
201 NtQueryInformationProcessPtr = (NtQueryInformationProcessProc)
202 GetProcAddress(NtDllModule, "NtQueryInformationProcess");
203 NtReadVirtualMemoryPtr = (NtReadVirtualMemoryProc)
204 GetProcAddress(NtDllModule, "NtReadVirtualMemory");
205 }
206
207 if (NULL == NtQueryInformationProcessPtr || NULL == NtReadVirtualMemoryPtr)
208 {
209 return TRUE;
210 }
211
212 Status = NtQueryInformationProcessPtr(Process, ProcessBasicInformation,
213 &Info, sizeof(PROCESS_BASIC_INFORMATION), NULL);
214 if (! NT_SUCCESS(Status))
215 {
216 #ifdef _DEBUG
217 DebugPrintf (_T("NtQueryInformationProcess failed with status %08x\n"), Status);
218 #endif
219 return TRUE;
220 }
221 Status = NtReadVirtualMemoryPtr(Process, Info.PebBaseAddress, &ProcessPeb,
222 sizeof(PEB), &BytesRead);
223 if (! NT_SUCCESS(Status) || sizeof(PEB) != BytesRead)
224 {
225 #ifdef _DEBUG
226 DebugPrintf (_T("Couldn't read virt mem status %08x bytes read %lu\n"), Status, BytesRead);
227 #endif
228 return TRUE;
229 }
230
231 return IMAGE_SUBSYSTEM_WINDOWS_CUI == ProcessPeb.ImageSubSystem;
232 }
233
234
235
236 #ifdef _UNICODE
237 #define SHELLEXECUTETEXT "ShellExecuteW"
238 #else
239 #define SHELLEXECUTETEXT "ShellExecuteA"
240 #endif
241
242 typedef HINSTANCE (WINAPI *MYEX)(
243 HWND hwnd,
244 LPCTSTR lpOperation,
245 LPCTSTR lpFile,
246 LPCTSTR lpParameters,
247 LPCTSTR lpDirectory,
248 INT nShowCmd
249 );
250
251
252
253 static BOOL RunFile(LPTSTR filename)
254 {
255 HMODULE hShell32;
256 MYEX hShExt;
257 HINSTANCE ret;
258
259 #ifdef _DEBUG
260 DebugPrintf (_T("RunFile(%s)\n"), filename);
261 #endif
262 hShell32 = LoadLibrary(_T("SHELL32.DLL"));
263 if (!hShell32)
264 {
265 #ifdef _DEBUG
266 DebugPrintf (_T("RunFile: couldn't load SHELL32.DLL!\n"));
267 #endif
268 return FALSE;
269 }
270
271 hShExt = (MYEX)(FARPROC)GetProcAddress(hShell32, SHELLEXECUTETEXT);
272 if (!hShExt)
273 {
274 #ifdef _DEBUG
275 DebugPrintf (_T("RunFile: couldn't find ShellExecuteA/W in SHELL32.DLL!\n"));
276 #endif
277 FreeLibrary(hShell32);
278 return FALSE;
279 }
280
281 #ifdef _DEBUG
282 DebugPrintf (_T("RunFile: ShellExecuteA/W is at %x\n"), hShExt);
283 #endif
284
285 ret = (hShExt)(NULL, _T("open"), filename, NULL, NULL, SW_SHOWNORMAL);
286
287 #ifdef _DEBUG
288 DebugPrintf (_T("RunFile: ShellExecuteA/W returned %d\n"), (DWORD)ret);
289 #endif
290
291 FreeLibrary(hShell32);
292 return (((DWORD)ret) > 32);
293 }
294
295
296
297 /*
298 * This command (in first) was not found in the command table
299 *
300 * first - first word on command line
301 * rest - rest of command line
302 */
303
304 static VOID
305 Execute (LPTSTR full, LPTSTR first, LPTSTR rest)
306 {
307 TCHAR szFullName[MAX_PATH];
308 #ifndef __REACTOS__
309 TCHAR szWindowTitle[MAX_PATH];
310 #endif
311 DWORD dwExitCode = 0;
312
313 #ifdef _DEBUG
314 DebugPrintf (_T("Execute: \'%s\' \'%s\'\n"), first, rest);
315 #endif
316
317 /* check for a drive change */
318 if ((_istalpha (first[0])) && (!_tcscmp (first + 1, _T(":"))))
319 {
320 BOOL working = TRUE;
321 if (!SetCurrentDirectory(first))
322 /* Guess they changed disc or something, handle that gracefully and get to root */
323 {
324 TCHAR str[4];
325 str[0]=first[0];
326 str[1]=_T(':');
327 str[2]=_T('\\');
328 str[3]=0;
329 working = SetCurrentDirectory(str);
330 }
331
332 if (!working) ConErrPuts (INVALIDDRIVE);
333
334 return;
335 }
336
337 /* get the PATH environment variable and parse it */
338 /* search the PATH environment variable for the binary */
339 if (!SearchForExecutable (first, szFullName))
340 {
341 error_bad_command ();
342 return;
343 }
344
345 #ifndef __REACTOS__
346 GetConsoleTitle (szWindowTitle, MAX_PATH);
347 #endif
348
349 /* check if this is a .BAT or .CMD file */
350 if (!_tcsicmp (_tcsrchr (szFullName, _T('.')), _T(".bat")) ||
351 !_tcsicmp (_tcsrchr (szFullName, _T('.')), _T(".cmd")))
352 {
353 #ifdef _DEBUG
354 DebugPrintf (_T("[BATCH: %s %s]\n"), szFullName, rest);
355 #endif
356 Batch (szFullName, first, rest);
357 }
358 else
359 {
360 /* exec the program */
361 PROCESS_INFORMATION prci;
362 STARTUPINFO stui;
363
364 #ifdef _DEBUG
365 DebugPrintf (_T("[EXEC: %s %s]\n"), full, rest);
366 #endif
367 /* build command line for CreateProcess() */
368
369 /* fill startup info */
370 memset (&stui, 0, sizeof (STARTUPINFO));
371 stui.cb = sizeof (STARTUPINFO);
372 stui.dwFlags = STARTF_USESHOWWINDOW;
373 stui.wShowWindow = SW_SHOWDEFAULT;
374
375 // return console to standard mode
376 SetConsoleMode (GetStdHandle(STD_INPUT_HANDLE),
377 ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT );
378
379 if (CreateProcess (szFullName,
380 full,
381 NULL,
382 NULL,
383 TRUE,
384 CREATE_NEW_PROCESS_GROUP,
385 NULL,
386 NULL,
387 &stui,
388 &prci))
389 {
390 if (IsConsoleProcess(prci.hProcess))
391 {
392 /* FIXME: Protect this with critical section */
393 bChildProcessRunning = TRUE;
394 dwChildProcessId = prci.dwProcessId;
395
396 WaitForSingleObject (prci.hProcess, INFINITE);
397
398 /* FIXME: Protect this with critical section */
399 bChildProcessRunning = FALSE;
400
401 GetExitCodeProcess (prci.hProcess, &dwExitCode);
402 nErrorLevel = (INT)dwExitCode;
403 }
404 CloseHandle (prci.hThread);
405 CloseHandle (prci.hProcess);
406 }
407 else
408 {
409 #ifdef _DEBUG
410 DebugPrintf (_T("[ShellExecute: %s]\n"), full);
411 #endif
412 // See if we can run this with ShellExecute() ie myfile.xls
413 if (!RunFile(full))
414 {
415 #ifdef _DEBUG
416 DebugPrintf (_T("[ShellExecute failed!: %s]\n"), full);
417 #endif
418 error_bad_command ();
419 }
420 }
421 // restore console mode
422 SetConsoleMode( GetStdHandle( STD_INPUT_HANDLE ),
423 ENABLE_PROCESSED_INPUT );
424 }
425
426 #ifndef __REACTOS__
427 SetConsoleTitle (szWindowTitle);
428 #endif
429 }
430
431
432 /*
433 * look through the internal commands and determine whether or not this
434 * command is one of them. If it is, call the command. If not, call
435 * execute to run it as an external program.
436 *
437 * line - the command line of the program to run
438 *
439 */
440
441 static VOID
442 DoCommand (LPTSTR line)
443 {
444 TCHAR com[CMDLINE_LENGTH]; /* the first word in the command */
445 LPTSTR cp = com;
446 LPTSTR cstart;
447 LPTSTR rest; /* pointer to the rest of the command line */
448 INT cl;
449 LPCOMMAND cmdptr;
450
451 #ifdef _DEBUG
452 DebugPrintf (_T("DoCommand: (\'%s\')\n"), line);
453 #endif /* DEBUG */
454
455 /* Skip over initial white space */
456 while (_istspace (*line))
457 line++;
458 rest = line;
459
460 cstart = rest;
461
462 /* Anything to do ? */
463 if (*rest)
464 {
465 if (*rest == _T('"'))
466 {
467 /* treat quoted words specially */
468
469 rest++;
470
471 while(*rest != _T('\0') && *rest != _T('"'))
472 *cp++ = _totlower (*rest++);
473 if (*rest == _T('"'))
474 rest++;
475 }
476 else
477 {
478 while (!IsDelimiter (*rest))
479 *cp++ = _totlower (*rest++);
480 }
481
482
483 /* Terminate first word */
484 *cp = _T('\0');
485
486 /* commands are limited to MAX_PATH */
487 if(_tcslen(com) > MAX_PATH)
488 {
489 error_bad_command();
490 return;
491 }
492
493 /* Skip over whitespace to rest of line */
494 while (_istspace (*rest))
495 rest++;
496
497 /* Scan internal command table */
498 for (cmdptr = cmds;; cmdptr++)
499 {
500 /* If end of table execute ext cmd */
501 if (cmdptr->name == NULL)
502 {
503 Execute (line, com, rest);
504 break;
505 }
506
507 if (!_tcscmp (com, cmdptr->name))
508 {
509 cmdptr->func (com, rest);
510 break;
511 }
512
513 /* The following code handles the case of commands like CD which
514 * are recognised even when the command name and parameter are
515 * not space separated.
516 *
517 * e.g dir..
518 * cd\freda
519 */
520
521 /* Get length of command name */
522 cl = _tcslen (cmdptr->name);
523
524 if ((cmdptr->flags & CMD_SPECIAL) &&
525 (!_tcsncmp (cmdptr->name, com, cl)) &&
526 (_tcschr (_T("\\.-"), *(com + cl))))
527 {
528 /* OK its one of the specials...*/
529
530 /* Terminate first word properly */
531 com[cl] = _T('\0');
532
533 /* Call with new rest */
534 cmdptr->func (com, cstart + cl);
535 break;
536 }
537 }
538 }
539 }
540
541
542 /*
543 * process the command line and execute the appropriate functions
544 * full input/output redirection and piping are supported
545 */
546
547 VOID ParseCommandLine (LPTSTR cmd)
548 {
549 TCHAR cmdline[CMDLINE_LENGTH];
550 LPTSTR s;
551 #ifdef FEATURE_REDIRECTION
552 TCHAR in[CMDLINE_LENGTH] = _T("");
553 TCHAR out[CMDLINE_LENGTH] = _T("");
554 TCHAR err[CMDLINE_LENGTH] = _T("");
555 TCHAR szTempPath[MAX_PATH] = _T(".\\");
556 TCHAR szFileName[2][MAX_PATH] = {_T(""), _T("")};
557 HANDLE hFile[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
558 LPTSTR t = NULL;
559 INT num = 0;
560 INT nRedirFlags = 0;
561 INT Length;
562 UINT Attributes;
563
564 HANDLE hOldConIn;
565 HANDLE hOldConOut;
566 HANDLE hOldConErr;
567 #endif /* FEATURE_REDIRECTION */
568
569 _tcscpy (cmdline, cmd);
570 s = &cmdline[0];
571
572 #ifdef _DEBUG
573 DebugPrintf (_T("ParseCommandLine: (\'%s\')\n"), s);
574 #endif /* DEBUG */
575
576 #ifdef FEATURE_ALIASES
577 /* expand all aliases */
578 ExpandAlias (s, CMDLINE_LENGTH);
579 #endif /* FEATURE_ALIAS */
580
581 #ifdef FEATURE_REDIRECTION
582 /* find the temp path to store temporary files */
583 Length = GetTempPath (MAX_PATH, szTempPath);
584 if (Length > 0 && Length < MAX_PATH)
585 {
586 Attributes = GetFileAttributes(szTempPath);
587 if (Attributes == 0xffffffff ||
588 !(Attributes & FILE_ATTRIBUTE_DIRECTORY))
589 {
590 Length = 0;
591 }
592 }
593 if (Length == 0 || Length >= MAX_PATH)
594 {
595 _tcscpy(szTempPath, _T(".\\"));
596 }
597 if (szTempPath[_tcslen (szTempPath) - 1] != _T('\\'))
598 _tcscat (szTempPath, _T("\\"));
599
600 /* get the redirections from the command line */
601 num = GetRedirection (s, in, out, err, &nRedirFlags);
602
603 /* more efficient, but do we really need to do this? */
604 for (t = in; _istspace (*t); t++)
605 ;
606 _tcscpy (in, t);
607
608 for (t = out; _istspace (*t); t++)
609 ;
610 _tcscpy (out, t);
611
612 for (t = err; _istspace (*t); t++)
613 ;
614 _tcscpy (err, t);
615
616 /* Set up the initial conditions ... */
617 /* preserve STDIN, STDOUT and STDERR handles */
618 hOldConIn = GetStdHandle (STD_INPUT_HANDLE);
619 hOldConOut = GetStdHandle (STD_OUTPUT_HANDLE);
620 hOldConErr = GetStdHandle (STD_ERROR_HANDLE);
621
622 /* redirect STDIN */
623 if (in[0])
624 {
625 HANDLE hFile;
626 SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
627
628 hFile = CreateFile (in, GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
629 FILE_ATTRIBUTE_NORMAL, NULL);
630 if (hFile == INVALID_HANDLE_VALUE)
631 {
632 ConErrPrintf (_T("Can't redirect input from file %s\n"), in);
633 return;
634 }
635
636 if (!SetStdHandle (STD_INPUT_HANDLE, hFile))
637 {
638 ConErrPrintf (_T("Can't redirect input from file %s\n"), in);
639 return;
640 }
641 #ifdef _DEBUG
642 DebugPrintf (_T("Input redirected from: %s\n"), in);
643 #endif
644 }
645
646 /* Now do all but the last pipe command */
647 *szFileName[0] = _T('\0');
648 hFile[0] = INVALID_HANDLE_VALUE;
649
650 while (num-- > 1)
651 {
652 SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
653
654 /* Create unique temporary file name */
655 GetTempFileName (szTempPath, _T("CMD"), 0, szFileName[1]);
656
657 /* Set current stdout to temporary file */
658 hFile[1] = CreateFile (szFileName[1], GENERIC_WRITE, 0, &sa,
659 TRUNCATE_EXISTING, FILE_ATTRIBUTE_TEMPORARY, NULL);
660
661 if (hFile[1] == INVALID_HANDLE_VALUE){
662 ConErrPrintf (_T("Error creating temporary file for pipe data\n"));
663 return;
664 }
665
666 SetStdHandle (STD_OUTPUT_HANDLE, hFile[1]);
667
668 DoCommand (s);
669
670 /* close stdout file */
671 SetStdHandle (STD_OUTPUT_HANDLE, hOldConOut);
672 if ((hFile[1] != INVALID_HANDLE_VALUE) && (hFile[1] != hOldConOut))
673 {
674 CloseHandle (hFile[1]);
675 hFile[1] = INVALID_HANDLE_VALUE;
676 }
677
678 /* close old stdin file */
679 SetStdHandle (STD_INPUT_HANDLE, hOldConIn);
680 if ((hFile[0] != INVALID_HANDLE_VALUE) && (hFile[0] != hOldConIn))
681 {
682 /* delete old stdin file, if it is a real file */
683 CloseHandle (hFile[0]);
684 hFile[0] = INVALID_HANDLE_VALUE;
685 DeleteFile (szFileName[0]);
686 *szFileName[0] = _T('\0');
687 }
688
689 /* copy stdout file name to stdin file name */
690 _tcscpy (szFileName[0], szFileName[1]);
691 *szFileName[1] = _T('\0');
692
693 /* open new stdin file */
694 hFile[0] = CreateFile (szFileName[0], GENERIC_READ, 0, &sa,
695 OPEN_EXISTING, FILE_ATTRIBUTE_TEMPORARY, NULL);
696 SetStdHandle (STD_INPUT_HANDLE, hFile[0]);
697
698 s = s + _tcslen (s) + 1;
699 }
700
701 /* Now set up the end conditions... */
702 /* redirect STDOUT */
703 if (out[0])
704 {
705 /* Final output to here */
706 HANDLE hFile;
707 SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
708
709 hFile = CreateFile (out, GENERIC_WRITE, FILE_SHARE_READ, &sa,
710 (nRedirFlags & OUTPUT_APPEND) ? OPEN_ALWAYS : CREATE_ALWAYS,
711 FILE_ATTRIBUTE_NORMAL, NULL);
712 if (hFile == INVALID_HANDLE_VALUE)
713 {
714 ConErrPrintf (_T("Can't redirect to file %s\n"), out);
715 return;
716 }
717
718 if (!SetStdHandle (STD_OUTPUT_HANDLE, hFile))
719 {
720 ConErrPrintf (_T("Can't redirect to file %s\n"), out);
721 return;
722 }
723
724 if (nRedirFlags & OUTPUT_APPEND)
725 {
726 LONG lHighPos = 0;
727
728 if (GetFileType (hFile) == FILE_TYPE_DISK)
729 SetFilePointer (hFile, 0, &lHighPos, FILE_END);
730 }
731 #ifdef _DEBUG
732 DebugPrintf (_T("Output redirected to: %s\n"), out);
733 #endif
734 }
735 else if (hOldConOut != INVALID_HANDLE_VALUE)
736 {
737 /* Restore original stdout */
738 HANDLE hOut = GetStdHandle (STD_OUTPUT_HANDLE);
739 SetStdHandle (STD_OUTPUT_HANDLE, hOldConOut);
740 if (hOldConOut != hOut)
741 CloseHandle (hOut);
742 hOldConOut = INVALID_HANDLE_VALUE;
743 }
744
745 /* redirect STDERR */
746 if (err[0])
747 {
748 /* Final output to here */
749 HANDLE hFile;
750 SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
751
752 if (!_tcscmp (err, out))
753 {
754 #ifdef _DEBUG
755 DebugPrintf (_T("Stdout and stderr will use the same file!!\n"));
756 #endif
757 DuplicateHandle (GetCurrentProcess (),
758 GetStdHandle (STD_OUTPUT_HANDLE),
759 GetCurrentProcess (),
760 &hFile, 0, TRUE, DUPLICATE_SAME_ACCESS);
761 }
762 else
763 {
764 hFile = CreateFile (err,
765 GENERIC_WRITE,
766 0,
767 &sa,
768 (nRedirFlags & ERROR_APPEND) ? OPEN_ALWAYS : CREATE_ALWAYS,
769 FILE_ATTRIBUTE_NORMAL,
770 NULL);
771 if (hFile == INVALID_HANDLE_VALUE)
772 {
773 ConErrPrintf (_T("Can't redirect to file %s\n"), err);
774 return;
775 }
776 }
777 if (!SetStdHandle (STD_ERROR_HANDLE, hFile))
778 {
779 ConErrPrintf (_T("Can't redirect to file %s\n"), err);
780 return;
781 }
782
783 if (nRedirFlags & ERROR_APPEND)
784 {
785 LONG lHighPos = 0;
786
787 if (GetFileType (hFile) == FILE_TYPE_DISK)
788 SetFilePointer (hFile, 0, &lHighPos, FILE_END);
789 }
790 #ifdef _DEBUG
791 DebugPrintf (_T("Error redirected to: %s\n"), err);
792 #endif
793 }
794 else if (hOldConErr != INVALID_HANDLE_VALUE)
795 {
796 /* Restore original stderr */
797 HANDLE hErr = GetStdHandle (STD_ERROR_HANDLE);
798 SetStdHandle (STD_ERROR_HANDLE, hOldConErr);
799 if (hOldConErr != hErr)
800 CloseHandle (hErr);
801 hOldConErr = INVALID_HANDLE_VALUE;
802 }
803 #endif
804
805 /* process final command */
806 DoCommand (s);
807
808 #ifdef FEATURE_REDIRECTION
809 /* close old stdin file */
810 #if 0 /* buggy implementation */
811 SetStdHandle (STD_INPUT_HANDLE, hOldConIn);
812 if ((hFile[0] != INVALID_HANDLE_VALUE) &&
813 (hFile[0] != hOldConIn))
814 {
815 /* delete old stdin file, if it is a real file */
816 CloseHandle (hFile[0]);
817 hFile[0] = INVALID_HANDLE_VALUE;
818 DeleteFile (szFileName[0]);
819 *szFileName[0] = _T('\0');
820 }
821
822 /* Restore original STDIN */
823 if (hOldConIn != INVALID_HANDLE_VALUE)
824 {
825 HANDLE hIn = GetStdHandle (STD_INPUT_HANDLE);
826 SetStdHandle (STD_INPUT_HANDLE, hOldConIn);
827 if (hOldConIn != hIn)
828 CloseHandle (hIn);
829 hOldConIn = INVALID_HANDLE_VALUE;
830 }
831 else
832 {
833 #ifdef _DEBUG
834 DebugPrintf (_T("Can't restore STDIN! Is invalid!!\n"), out);
835 #endif
836 }
837 #endif /* buggy implementation */
838
839
840 if (hOldConIn != INVALID_HANDLE_VALUE)
841 {
842 HANDLE hIn = GetStdHandle (STD_INPUT_HANDLE);
843 SetStdHandle (STD_INPUT_HANDLE, hOldConIn);
844 if (hIn == INVALID_HANDLE_VALUE)
845 {
846 #ifdef _DEBUG
847 DebugPrintf (_T("Previous STDIN is invalid!!\n"));
848 #endif
849 }
850 else
851 {
852 if (GetFileType (hIn) == FILE_TYPE_DISK)
853 {
854 if (hFile[0] == hIn)
855 {
856 CloseHandle (hFile[0]);
857 hFile[0] = INVALID_HANDLE_VALUE;
858 DeleteFile (szFileName[0]);
859 *szFileName[0] = _T('\0');
860 }
861 else
862 {
863 #ifdef _DEBUG
864 DebugPrintf (_T("hFile[0] and hIn dont match!!!\n"));
865 #endif
866 }
867 }
868 }
869 }
870
871
872 /* Restore original STDOUT */
873 if (hOldConOut != INVALID_HANDLE_VALUE)
874 {
875 HANDLE hOut = GetStdHandle (STD_OUTPUT_HANDLE);
876 SetStdHandle (STD_OUTPUT_HANDLE, hOldConOut);
877 if (hOldConOut != hOut)
878 CloseHandle (hOut);
879 hOldConOut = INVALID_HANDLE_VALUE;
880 }
881
882 /* Restore original STDERR */
883 if (hOldConErr != INVALID_HANDLE_VALUE)
884 {
885 HANDLE hErr = GetStdHandle (STD_ERROR_HANDLE);
886 SetStdHandle (STD_ERROR_HANDLE, hOldConErr);
887 if (hOldConErr != hErr)
888 CloseHandle (hErr);
889 hOldConErr = INVALID_HANDLE_VALUE;
890 }
891 #endif /* FEATURE_REDIRECTION */
892 }
893
894
895 /*
896 * do the prompt/input/process loop
897 *
898 */
899
900 static INT
901 ProcessInput (BOOL bFlag)
902 {
903 TCHAR commandline[CMDLINE_LENGTH];
904 TCHAR readline[CMDLINE_LENGTH];
905 LPTSTR tp = NULL;
906 LPTSTR ip;
907 LPTSTR cp;
908 BOOL bEchoThisLine;
909
910 do
911 {
912 /* if no batch input then... */
913 if (!(ip = ReadBatchLine (&bEchoThisLine)))
914 {
915 if (bFlag)
916 return 0;
917
918 ReadCommand (readline, CMDLINE_LENGTH);
919 ip = readline;
920 bEchoThisLine = FALSE;
921 }
922
923 cp = commandline;
924 while (*ip)
925 {
926 if (*ip == _T('%'))
927 {
928 switch (*++ip)
929 {
930 case _T('%'):
931 *cp++ = *ip++;
932 break;
933
934 case _T('0'):
935 case _T('1'):
936 case _T('2'):
937 case _T('3'):
938 case _T('4'):
939 case _T('5'):
940 case _T('6'):
941 case _T('7'):
942 case _T('8'):
943 case _T('9'):
944 if ((tp = FindArg (*ip - _T('0'))))
945 {
946 cp = _stpcpy (cp, tp);
947 ip++;
948 }
949 else
950 *cp++ = _T('%');
951 break;
952
953 case _T('?'):
954 cp += _stprintf (cp, _T("%u"), nErrorLevel);
955 ip++;
956 break;
957
958 default:
959 tp = _tcschr(ip, _T('%'));
960 if ((tp != NULL) &&
961 (tp <= _tcschr(ip, _T(' ')) - 1))
962 {
963 TCHAR evar[512];
964 *tp = _T('\0');
965
966 /* FIXME: This is just a quick hack!! */
967 /* Do a proper memory allocation!! */
968 if (GetEnvironmentVariable (ip, evar, 512))
969 cp = _stpcpy (cp, evar);
970
971 ip = tp + 1;
972 }
973 else
974 {
975 *cp++ = _T('%');
976 }
977 break;
978 }
979 continue;
980 }
981
982 if (_istcntrl (*ip))
983 *ip = _T(' ');
984 *cp++ = *ip++;
985 }
986
987 *cp = _T('\0');
988
989 /* strip trailing spaces */
990 while ((--cp >= commandline) && _istspace (*cp));
991
992 *(cp + 1) = _T('\0');
993
994 /* JPP 19980807 */
995 /* Echo batch file line */
996 if (bEchoThisLine)
997 {
998 PrintPrompt ();
999 ConOutPuts (commandline);
1000 }
1001
1002 if (*commandline)
1003 {
1004 ParseCommandLine (commandline);
1005 if (bEcho && !bIgnoreEcho)
1006 ConOutChar ('\n');
1007 bIgnoreEcho = FALSE;
1008 }
1009 }
1010 while (!bCanExit || !bExit);
1011
1012 return 0;
1013 }
1014
1015
1016 /*
1017 * control-break handler.
1018 */
1019 BOOL WINAPI BreakHandler (DWORD dwCtrlType)
1020 {
1021 if ((dwCtrlType != CTRL_C_EVENT) &&
1022 (dwCtrlType != CTRL_BREAK_EVENT))
1023 return FALSE;
1024
1025 if (bChildProcessRunning == TRUE)
1026 {
1027 GenerateConsoleCtrlEvent (CTRL_C_EVENT,
1028 dwChildProcessId);
1029 return TRUE;
1030 }
1031
1032 /* FIXME: Handle batch files */
1033
1034 /* FIXME: Print "^C" */
1035
1036
1037 return TRUE;
1038 }
1039
1040
1041 VOID AddBreakHandler (VOID)
1042 {
1043 SetConsoleCtrlHandler ((PHANDLER_ROUTINE)BreakHandler, TRUE);
1044 }
1045
1046
1047 VOID RemoveBreakHandler (VOID)
1048 {
1049 SetConsoleCtrlHandler ((PHANDLER_ROUTINE)BreakHandler, FALSE);
1050 }
1051
1052
1053 /*
1054 * show commands and options that are available.
1055 *
1056 */
1057 #if 0
1058 static VOID
1059 ShowCommands (VOID)
1060 {
1061 /* print command list */
1062 ConOutPrintf (_T("\nInternal commands available:\n"));
1063 PrintCommandList ();
1064
1065 /* print feature list */
1066 ConOutPuts (_T("\nFeatures available:"));
1067 #ifdef FEATURE_ALIASES
1068 ConOutPuts (_T(" [aliases]"));
1069 #endif
1070 #ifdef FEATURE_HISTORY
1071 ConOutPuts (_T(" [history]"));
1072 #endif
1073 #ifdef FEATURE_UNIX_FILENAME_COMPLETION
1074 ConOutPuts (_T(" [unix filename completion]"));
1075 #endif
1076 #ifdef FEATURE_DIRECTORY_STACK
1077 ConOutPuts (_T(" [directory stack]"));
1078 #endif
1079 #ifdef FEATURE_REDIRECTION
1080 ConOutPuts (_T(" [redirections and piping]"));
1081 #endif
1082 ConOutChar (_T('\n'));
1083 }
1084 #endif
1085
1086 /*
1087 * set up global initializations and process parameters
1088 *
1089 * argc - number of parameters to command.com
1090 * argv - command-line parameters
1091 *
1092 */
1093 static VOID
1094 Initialize (int argc, TCHAR* argv[])
1095 {
1096 TCHAR commandline[CMDLINE_LENGTH];
1097 TCHAR ModuleName[_MAX_PATH + 1];
1098 INT i;
1099 //INT len;
1100 //TCHAR *ptr, *cmdLine;
1101
1102
1103 #ifdef _DEBUG
1104 INT x;
1105
1106 DebugPrintf (_T("[command args:\n"));
1107 for (x = 0; x < argc; x++)
1108 {
1109 DebugPrintf (_T("%d. %s\n"), x, argv[x]);
1110 }
1111 DebugPrintf (_T("]\n"));
1112 #endif
1113
1114 /* get version information */
1115 osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
1116 GetVersionEx (&osvi);
1117
1118 InitLocale ();
1119
1120 /* get default input and output console handles */
1121 hOut = GetStdHandle (STD_OUTPUT_HANDLE);
1122 hIn = GetStdHandle (STD_INPUT_HANDLE);
1123
1124
1125 if (argc >= 2 && !_tcsncmp (argv[1], _T("/?"), 2))
1126 {
1127 ConOutPuts (_T("Starts a new instance of the ReactOS command line interpreter.\n"
1128 "\n"
1129 "CMD [/[C|K] command][/P][/Q][/T:bf]\n"
1130 "\n"
1131 " /C command Runs the specified command and terminates.\n"
1132 " /K command Runs the specified command and remains.\n"
1133 " /P CMD becomes permanent and runs autoexec.bat\n"
1134 " (cannot be terminated).\n"
1135 " /T:bf Sets the background/foreground color (see COLOR command)."));
1136 ExitProcess (0);
1137 }
1138 SetConsoleMode (hIn, ENABLE_PROCESSED_INPUT);
1139
1140 #ifdef INCLUDE_CMD_CHDIR
1141 InitLastPath ();
1142 #endif
1143
1144 #ifdef FATURE_ALIASES
1145 InitializeAlias ();
1146 #endif
1147
1148 if (argc >= 2)
1149 {
1150 for (i = 1; i < argc; i++)
1151 {
1152 if (!_tcsicmp (argv[i], _T("/p")))
1153 {
1154 if (!IsExistingFile (_T("\\autoexec.bat")))
1155 {
1156 #ifdef INCLUDE_CMD_DATE
1157 cmd_date (_T(""), _T(""));
1158 #endif
1159 #ifdef INCLUDE_CMD_TIME
1160 cmd_time (_T(""), _T(""));
1161 #endif
1162 }
1163 else
1164 {
1165 ParseCommandLine (_T("\\autoexec.bat"));
1166 }
1167 bCanExit = FALSE;
1168 }
1169 else if (!_tcsicmp (argv[i], _T("/c")))
1170 {
1171 /* This just runs a program and exits */
1172 ++i;
1173 if (i < argc)
1174 {
1175 _tcscpy (commandline, argv[i]);
1176 while (++i < argc)
1177 {
1178 _tcscat (commandline, _T(" "));
1179 _tcscat (commandline, argv[i]);
1180 }
1181
1182 ParseCommandLine(commandline);
1183 ExitProcess (ProcessInput (TRUE));
1184 }
1185 else
1186 {
1187 ExitProcess (0);
1188 }
1189 }
1190 else if (!_tcsicmp (argv[i], _T("/k")))
1191 {
1192 /* This just runs a program and remains */
1193 ++i;
1194 if (i < argc)
1195 {
1196 _tcscpy (commandline, argv[i]);
1197 while (++i < argc)
1198 {
1199 _tcscat (commandline, _T(" "));
1200 _tcscat (commandline, argv[i]);
1201 }
1202
1203 ParseCommandLine(commandline);
1204 }
1205 }
1206 #ifdef INCLUDE_CMD_COLOR
1207 else if (!_tcsnicmp (argv[i], _T("/t:"), 3))
1208 {
1209 /* process /t (color) argument */
1210 wDefColor = (WORD)_tcstoul (&argv[i][3], NULL, 16);
1211 wColor = wDefColor;
1212 SetScreenColor (wColor, TRUE);
1213 }
1214 #endif
1215 }
1216 }
1217
1218 /* run cmdstart.bat */
1219 if (IsExistingFile (_T("cmdstart.bat")))
1220 {
1221 ParseCommandLine (_T("cmdstart.bat"));
1222 }
1223 else if (IsExistingFile (_T("\\cmdstart.bat")))
1224 {
1225 ParseCommandLine (_T("\\cmdstart.bat"));
1226 }
1227 #ifndef __REACTOS__
1228 else
1229 {
1230 /* try to run cmdstart.bat from install dir */
1231 LPTSTR p;
1232
1233 _tcscpy (commandline, argv[0]);
1234 p = _tcsrchr (commandline, _T('\\')) + 1;
1235 _tcscpy (p, _T("cmdstart.bat"));
1236
1237 if (IsExistingFile (_T("commandline")))
1238 {
1239 ConErrPrintf (_T("Running %s...\n", commandline));
1240 ParseCommandLine (commandline);
1241 }
1242 }
1243 #endif
1244
1245 #ifdef FEATURE_DIR_STACK
1246 /* initialize directory stack */
1247 InitDirectoryStack ();
1248 #endif
1249
1250
1251 #ifdef FEATURE_HISTORY
1252 /*initialize history*/
1253 InitHistory();
1254 #endif
1255
1256 /* Set COMSPEC environment variable */
1257 if (0 != GetModuleFileName (NULL, ModuleName, _MAX_PATH + 1))
1258 {
1259 ModuleName[_MAX_PATH] = _T('\0');
1260 SetEnvironmentVariable (_T("COMSPEC"), ModuleName);
1261 }
1262
1263 /* add ctrl break handler */
1264 AddBreakHandler ();
1265 }
1266
1267
1268 static VOID Cleanup (int argc, TCHAR *argv[])
1269 {
1270 /* run cmdexit.bat */
1271 if (IsExistingFile (_T("cmdexit.bat")))
1272 {
1273 ConErrPrintf (_T("Running cmdexit.bat...\n"));
1274 ParseCommandLine (_T("cmdexit.bat"));
1275 }
1276 else if (IsExistingFile (_T("\\cmdexit.bat")))
1277 {
1278 ConErrPrintf (_T("Running \\cmdexit.bat...\n"));
1279 ParseCommandLine (_T("\\cmdexit.bat"));
1280 }
1281 #ifndef __REACTOS__
1282 else
1283 {
1284 /* try to run cmdexit.bat from install dir */
1285 TCHAR commandline[CMDLINE_LENGTH];
1286 LPTSTR p;
1287
1288 _tcscpy (commandline, argv[0]);
1289 p = _tcsrchr (commandline, _T('\\')) + 1;
1290 _tcscpy (p, _T("cmdexit.bat"));
1291
1292 if (IsExistingFile (_T("commandline")))
1293 {
1294 ConErrPrintf (_T("Running %s...\n"), commandline);
1295 ParseCommandLine (commandline);
1296 }
1297 }
1298 #endif
1299
1300 #ifdef FEATURE_ALIASES
1301 DestroyAlias ();
1302 #endif
1303
1304 #ifdef FEATURE_DIECTORY_STACK
1305 /* destroy directory stack */
1306 DestroyDirectoryStack ();
1307 #endif
1308
1309 #ifdef INCLUDE_CMD_CHDIR
1310 FreeLastPath ();
1311 #endif
1312
1313 #ifdef FEATURE_HISTORY
1314 CleanHistory();
1315 #endif
1316
1317
1318 /* remove ctrl break handler */
1319 RemoveBreakHandler ();
1320 SetConsoleMode( GetStdHandle( STD_INPUT_HANDLE ),
1321 ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT );
1322 }
1323
1324 #ifdef __REACTOS__
1325 #ifdef _UNICODE
1326 PWCHAR * _CommandLineToArgvW(PWCHAR lpCmdLine, int *pNumArgs)
1327 {
1328 PWCHAR * argvw = NULL;
1329 PWCHAR ptr = lpCmdLine;
1330 PWCHAR str;
1331 int len;
1332 int NumArgs;
1333
1334 NumArgs = 0;
1335
1336 while(lpCmdLine && *lpCmdLine)
1337 {
1338 while (iswspace(*lpCmdLine)) lpCmdLine++;
1339 if (*lpCmdLine)
1340 {
1341 if ((NumArgs % 10)==0)
1342 {
1343 PWCHAR * old_argvw = argvw;
1344 argvw = malloc((NumArgs + 10) * sizeof(PWCHAR));
1345 memcpy(argvw, old_argvw, NumArgs * sizeof(PWCHAR));
1346 free(old_argvw);
1347 }
1348 ptr = wcschr(lpCmdLine, L' ');
1349 if (ptr)
1350 {
1351 len = ptr - lpCmdLine;
1352 }
1353 else
1354 {
1355 len = wcslen(lpCmdLine);
1356 }
1357 str = malloc((len + 1) * sizeof(WCHAR));
1358 memcpy(str, lpCmdLine, len * sizeof(WCHAR));
1359 str[len] = 0;
1360 argvw[NumArgs]=str;
1361 NumArgs++;
1362 lpCmdLine = ptr;
1363 }
1364 }
1365 *pNumArgs = NumArgs;
1366 return argvw;
1367 }
1368 #endif
1369 #endif
1370
1371 /*
1372 * main function
1373 */
1374 #ifdef _UNICODE
1375 int main(void)
1376 #else
1377 int main (int argc, char *argv[])
1378 #endif
1379 {
1380 CONSOLE_SCREEN_BUFFER_INFO Info;
1381 INT nExitCode;
1382 #ifdef _UNICODE
1383 PWCHAR * argv;
1384 int argc=0;
1385 #ifdef __REACTOS__
1386 argv = _CommandLineToArgvW(GetCommandLineW(), &argc);
1387 #else
1388 argv = CommandLineToArgvW(GetCommandLineW(), &argc);
1389 #endif
1390 #endif
1391
1392 SetFileApisToOEM();
1393
1394 hConsole = CreateFile(_T("CONOUT$"), GENERIC_READ|GENERIC_WRITE,
1395 FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
1396 OPEN_EXISTING, 0, NULL);
1397 if (GetConsoleScreenBufferInfo(hConsole, &Info) == FALSE)
1398 {
1399 ConOutFormatMessage(GetLastError());
1400 return(1);
1401 }
1402 wColor = Info.wAttributes;
1403 wDefColor = wColor;
1404
1405 /* check switches on command-line */
1406 Initialize(argc, argv);
1407
1408 /* call prompt routine */
1409 nExitCode = ProcessInput(FALSE);
1410
1411 /* do the cleanup */
1412 Cleanup(argc, argv);
1413
1414 return(nExitCode);
1415 }
1416
1417 /* EOF */