Various improvements by Carl Nettelblad.
[reactos.git] / rosapps / cmd / cmd.c
1 /* $Id: cmd.c,v 1.24 2001/02/28 22:33:23 ekohl Exp $
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
124 #include "config.h"
125
126 #include <windows.h>
127 #include <tchar.h>
128 #include <string.h>
129 #include <stdlib.h>
130 #include <ctype.h>
131 #include <stdio.h>
132
133 #include "cmd.h"
134 #include "batch.h"
135
136
137 BOOL bExit = FALSE; /* indicates EXIT was typed */
138 BOOL bCanExit = TRUE; /* indicates if this shell is exitable */
139 BOOL bCtrlBreak = FALSE; /* Ctrl-Break or Ctrl-C hit */
140 BOOL bIgnoreEcho = FALSE; /* Ignore 'newline' before 'cls' */
141 INT nErrorLevel = 0; /* Errorlevel of last launched external program */
142 BOOL bChildProcessRunning = FALSE;
143 DWORD dwChildProcessId = 0;
144 OSVERSIONINFO osvi;
145 HANDLE hIn;
146 HANDLE hOut;
147
148 #ifdef INCLUDE_CMD_COLOR
149 WORD wColor; /* current color */
150 WORD wDefColor; /* default color */
151 #endif
152
153
154 /*
155 * is character a delimeter when used on first word?
156 *
157 */
158
159 static BOOL IsDelimiter (TCHAR c)
160 {
161 return (c == _T('/') || c == _T('=') || c == _T('\0') || _istspace (c));
162 }
163
164
165 /*
166 * This command (in first) was not found in the command table
167 *
168 * first - first word on command line
169 * rest - rest of command line
170 */
171
172 static VOID
173 Execute (LPTSTR first, LPTSTR rest)
174 {
175 TCHAR szFullName[MAX_PATH];
176 #ifndef __REACTOS__
177 TCHAR szWindowTitle[MAX_PATH];
178 #endif
179 DWORD dwExitCode = 0;
180
181 #ifdef _DEBUG
182 DebugPrintf ("Execute: \'%s\' \'%s\'\n", first, rest);
183 #endif
184
185 /* check for a drive change */
186 if ((_istalpha (first[0])) && (!_tcscmp (first + 1, _T(":"))))
187 {
188 TCHAR szPath[MAX_PATH];
189 TCHAR szVar[5];
190
191 #ifdef _DEBUG
192 DebugPrintf ("Drive change to drive %s\n", first);
193 #endif
194 /* save curent directory in environment variable */
195 GetCurrentDirectory (MAX_PATH, szPath);
196
197 _tcscpy (szVar, _T("=A:"));
198 szVar[1] = _totupper (szPath[0]);
199
200 SetEnvironmentVariable (szVar, szPath);
201
202
203 /* check for current directory of new drive */
204 _tcscpy (szVar, _T("=A:"));
205 szVar[1] = _totupper (*first);
206
207 if (GetEnvironmentVariable (szVar, szPath, MAX_PATH) == 0)
208 {
209 /* no environment variable found */
210 _tcscpy (szPath, _T("A:\\"));
211 szPath[0] = _totupper (*first);
212 }
213
214 #ifdef _DEBUG
215 DebugPrintf ("Drive change to drive %s\n", szPath);
216 #endif
217
218 /* set new current directory */
219 SetCurrentDirectory (szPath);
220 GetCurrentDirectory (MAX_PATH, szPath);
221 if (szPath[0] != (TCHAR)_totupper (*first))
222 ConErrPuts (INVALIDDRIVE);
223
224 return;
225 }
226
227 /* get the PATH environment variable and parse it */
228 /* search the PATH environment variable for the binary */
229 if (!SearchForExecutable (first, szFullName))
230 {
231 error_bad_command ();
232 return;
233 }
234
235 #ifndef __REACTOS__
236 GetConsoleTitle (szWindowTitle, MAX_PATH);
237 #endif
238
239 /* check if this is a .BAT or .CMD file */
240 if (!_tcsicmp (_tcsrchr (szFullName, _T('.')), _T(".bat")) ||
241 !_tcsicmp (_tcsrchr (szFullName, _T('.')), _T(".cmd")))
242 {
243 #ifdef _DEBUG
244 DebugPrintf ("[BATCH: %s %s]\n", szFullName, rest);
245 #endif
246 Batch (szFullName, first, rest);
247 }
248 else
249 {
250 /* exec the program */
251 #ifndef __REACTOS__
252 TCHAR szFullCmdLine [CMDLINE_LENGTH];
253 #endif
254 PROCESS_INFORMATION prci;
255 STARTUPINFO stui;
256
257 #ifdef _DEBUG
258 DebugPrintf ("[EXEC: %s %s]\n", szFullName, rest);
259 #endif
260 #ifndef __REACTOS__
261 /* build command line for CreateProcess() */
262 _tcscpy (szFullCmdLine, szFullName);
263 _tcscat (szFullCmdLine, _T(" "));
264 _tcscat (szFullCmdLine, rest);
265 #endif
266
267 /* fill startup info */
268 memset (&stui, 0, sizeof (STARTUPINFO));
269 stui.cb = sizeof (STARTUPINFO);
270 stui.dwFlags = STARTF_USESHOWWINDOW;
271 stui.wShowWindow = SW_SHOWDEFAULT;
272
273 #ifndef __REACTOS__
274 if (CreateProcess (NULL,
275 szFullCmdLine,
276 NULL,
277 NULL,
278 FALSE,
279 CREATE_NEW_PROCESS_GROUP,
280 NULL,
281 NULL,
282 &stui,
283 &prci))
284 #else
285 if (CreateProcess (szFullName,
286 rest,
287 NULL,
288 NULL,
289 FALSE,
290 0,
291 NULL,
292 NULL,
293 &stui,
294 &prci))
295 #endif
296 {
297 /* FIXME: Protect this with critical section */
298 bChildProcessRunning = TRUE;
299 dwChildProcessId = prci.dwProcessId;
300
301 WaitForSingleObject (prci.hProcess, INFINITE);
302
303 /* FIXME: Protect this with critical section */
304 bChildProcessRunning = TRUE;
305
306 GetExitCodeProcess (prci.hProcess, &dwExitCode);
307 nErrorLevel = (INT)dwExitCode;
308 CloseHandle (prci.hThread);
309 CloseHandle (prci.hProcess);
310 }
311 else
312 {
313 ErrorMessage (GetLastError (),
314 "Error executing CreateProcess()!!\n");
315 }
316 }
317
318 #ifndef __REACTOS__
319 SetConsoleTitle (szWindowTitle);
320 #endif
321 }
322
323
324 /*
325 * look through the internal commands and determine whether or not this
326 * command is one of them. If it is, call the command. If not, call
327 * execute to run it as an external program.
328 *
329 * line - the command line of the program to run
330 *
331 */
332
333 static VOID
334 DoCommand (LPTSTR line)
335 {
336 TCHAR com[MAX_PATH]; /* the first word in the command */
337 LPTSTR cp = com;
338 LPTSTR cstart;
339 LPTSTR rest = line; /* pointer to the rest of the command line */
340 INT cl;
341 LPCOMMAND cmdptr;
342
343 #ifdef _DEBUG
344 DebugPrintf ("DoCommand: (\'%s\')\n", line);
345 #endif /* DEBUG */
346
347 /* Skip over initial white space */
348 while (isspace (*rest))
349 rest++;
350
351 cstart = rest;
352
353 /* Anything to do ? */
354 if (*rest)
355 {
356 /* Copy over 1st word as lower case */
357 while (!IsDelimiter (*rest))
358 *cp++ = _totlower (*rest++);
359
360 /* Terminate first word */
361 *cp = _T('\0');
362
363 /* Skip over whitespace to rest of line */
364 while (_istspace (*rest))
365 rest++;
366
367 /* Scan internal command table */
368 for (cmdptr = cmds;; cmdptr++)
369 {
370 /* If end of table execute ext cmd */
371 if (cmdptr->name == NULL)
372 {
373 Execute (com, rest);
374 break;
375 }
376
377 if (!_tcscmp (com, cmdptr->name))
378 {
379 cmdptr->func (com, rest);
380 break;
381 }
382
383 /* The following code handles the case of commands like CD which
384 * are recognised even when the command name and parameter are
385 * not space separated.
386 *
387 * e.g dir..
388 * cd\freda
389 */
390
391 /* Get length of command name */
392 cl = _tcslen (cmdptr->name);
393
394 if ((cmdptr->flags & CMD_SPECIAL) &&
395 (!_tcsncmp (cmdptr->name, com, cl)) &&
396 (_tcschr (_T("\\.-"), *(com + cl))))
397 {
398 /* OK its one of the specials...*/
399
400 /* Terminate first word properly */
401 com[cl] = _T('\0');
402
403 /* Call with new rest */
404 cmdptr->func (com, cstart + cl);
405 break;
406 }
407 }
408 }
409 }
410
411
412 /*
413 * process the command line and execute the appropriate functions
414 * full input/output redirection and piping are supported
415 */
416
417 VOID ParseCommandLine (LPTSTR cmd)
418 {
419 TCHAR cmdline[CMDLINE_LENGTH];
420 LPTSTR s;
421 #ifdef FEATURE_REDIRECTION
422 TCHAR in[CMDLINE_LENGTH] = "";
423 TCHAR out[CMDLINE_LENGTH] = "";
424 TCHAR err[CMDLINE_LENGTH] = "";
425 TCHAR szTempPath[MAX_PATH] = _T(".\\");
426 TCHAR szFileName[2][MAX_PATH] = {"", ""};
427 HANDLE hFile[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
428 LPTSTR t = NULL;
429 INT num = 0;
430 INT nRedirFlags = 0;
431
432 HANDLE hOldConIn;
433 HANDLE hOldConOut;
434 HANDLE hOldConErr;
435 #endif /* FEATURE_REDIRECTION */
436
437 _tcscpy (cmdline, cmd);
438 s = &cmdline[0];
439
440 #ifdef _DEBUG
441 DebugPrintf ("ParseCommandLine: (\'%s\')\n", s);
442 #endif /* DEBUG */
443
444 #ifdef FEATURE_ALIASES
445 /* expand all aliases */
446 ExpandAlias (s, CMDLINE_LENGTH);
447 #endif /* FEATURE_ALIAS */
448
449 #ifdef FEATURE_REDIRECTION
450 /* find the temp path to store temporary files */
451 GetTempPath (MAX_PATH, szTempPath);
452 if (szTempPath[_tcslen (szTempPath) - 1] != _T('\\'))
453 _tcscat (szTempPath, _T("\\"));
454
455 /* get the redirections from the command line */
456 num = GetRedirection (s, in, out, err, &nRedirFlags);
457
458 /* more efficient, but do we really need to do this? */
459 for (t = in; _istspace (*t); t++)
460 ;
461 _tcscpy (in, t);
462
463 for (t = out; _istspace (*t); t++)
464 ;
465 _tcscpy (out, t);
466
467 for (t = err; _istspace (*t); t++)
468 ;
469 _tcscpy (err, t);
470
471 /* Set up the initial conditions ... */
472 /* preserve STDIN, STDOUT and STDERR handles */
473 hOldConIn = GetStdHandle (STD_INPUT_HANDLE);
474 hOldConOut = GetStdHandle (STD_OUTPUT_HANDLE);
475 hOldConErr = GetStdHandle (STD_ERROR_HANDLE);
476
477 /* redirect STDIN */
478 if (in[0])
479 {
480 HANDLE hFile;
481
482 hFile = CreateFile (in, GENERIC_READ, 0, NULL, OPEN_EXISTING,
483 FILE_ATTRIBUTE_NORMAL, NULL);
484 if (hFile == INVALID_HANDLE_VALUE)
485 {
486 ConErrPrintf ("Can't redirect input from file %s\n", in);
487 return;
488 }
489
490 if (!SetStdHandle (STD_INPUT_HANDLE, hFile))
491 {
492 ConErrPrintf ("Can't redirect input from file %s\n", in);
493 return;
494 }
495 #ifdef _DEBUG
496 DebugPrintf (_T("Input redirected from: %s\n"), in);
497 #endif
498 }
499
500 /* Now do all but the last pipe command */
501 *szFileName[0] = '\0';
502 hFile[0] = INVALID_HANDLE_VALUE;
503
504 while (num-- > 1)
505 {
506 /* Create unique temporary file name */
507 GetTempFileName (szTempPath, "CMD", 0, szFileName[1]);
508
509 /* Set current stdout to temporary file */
510 hFile[1] = CreateFile (szFileName[1], GENERIC_WRITE, 0, NULL,
511 TRUNCATE_EXISTING, FILE_ATTRIBUTE_TEMPORARY, NULL);
512 SetStdHandle (STD_OUTPUT_HANDLE, hFile[1]);
513
514 DoCommand (s);
515
516 /* close stdout file */
517 SetStdHandle (STD_OUTPUT_HANDLE, hOldConOut);
518 if ((hFile[1] != INVALID_HANDLE_VALUE) && (hFile[1] != hOldConOut))
519 {
520 CloseHandle (hFile[1]);
521 hFile[1] = INVALID_HANDLE_VALUE;
522 }
523
524 /* close old stdin file */
525 SetStdHandle (STD_INPUT_HANDLE, hOldConIn);
526 if ((hFile[0] != INVALID_HANDLE_VALUE) && (hFile[0] != hOldConIn))
527 {
528 /* delete old stdin file, if it is a real file */
529 CloseHandle (hFile[0]);
530 hFile[0] = INVALID_HANDLE_VALUE;
531 DeleteFile (szFileName[0]);
532 *szFileName[0] = _T('\0');
533 }
534
535 /* copy stdout file name to stdin file name */
536 _tcscpy (szFileName[0], szFileName[1]);
537 *szFileName[1] = _T('\0');
538
539 /* open new stdin file */
540 hFile[0] = CreateFile (szFileName[0], GENERIC_READ, 0, NULL,
541 OPEN_EXISTING, FILE_ATTRIBUTE_TEMPORARY, NULL);
542 SetStdHandle (STD_INPUT_HANDLE, hFile[0]);
543
544 s = s + _tcslen (s) + 1;
545 }
546
547 /* Now set up the end conditions... */
548 /* redirect STDOUT */
549 if (out[0])
550 {
551 /* Final output to here */
552 HANDLE hFile;
553
554 hFile = CreateFile (out, GENERIC_WRITE, 0, NULL,
555 (nRedirFlags & OUTPUT_APPEND) ? OPEN_ALWAYS : CREATE_ALWAYS,
556 FILE_ATTRIBUTE_NORMAL, NULL);
557 if (hFile == INVALID_HANDLE_VALUE)
558 {
559 ConErrPrintf ("Can't redirect to file %s\n", out);
560 return;
561 }
562
563 if (!SetStdHandle (STD_OUTPUT_HANDLE, hFile))
564 {
565 ConErrPrintf ("Can't redirect to file %s\n", out);
566 return;
567 }
568
569 if (nRedirFlags & OUTPUT_APPEND)
570 {
571 LONG lHighPos = 0;
572
573 if (GetFileType (hFile) == FILE_TYPE_DISK)
574 SetFilePointer (hFile, 0, &lHighPos, FILE_END);
575 }
576 #ifdef _DEBUG
577 DebugPrintf (_T("Output redirected to: %s\n"), out);
578 #endif
579 }
580 else if (hOldConOut != INVALID_HANDLE_VALUE)
581 {
582 /* Restore original stdout */
583 HANDLE hOut = GetStdHandle (STD_OUTPUT_HANDLE);
584 SetStdHandle (STD_OUTPUT_HANDLE, hOldConOut);
585 if (hOldConOut != hOut)
586 CloseHandle (hOut);
587 hOldConOut = INVALID_HANDLE_VALUE;
588 }
589
590 /* redirect STDERR */
591 if (err[0])
592 {
593 /* Final output to here */
594 HANDLE hFile;
595
596 if (!_tcscmp (err, out))
597 {
598 #ifdef _DEBUG
599 DebugPrintf (_T("Stdout and stderr will use the same file!!\n"));
600 #endif
601 DuplicateHandle (GetCurrentProcess (),
602 GetStdHandle (STD_OUTPUT_HANDLE),
603 GetCurrentProcess (),
604 &hFile, 0, TRUE, DUPLICATE_SAME_ACCESS);
605 }
606 else
607 {
608 hFile = CreateFile (err,
609 GENERIC_WRITE,
610 0,
611 NULL,
612 (nRedirFlags & ERROR_APPEND) ? OPEN_ALWAYS : CREATE_ALWAYS,
613 FILE_ATTRIBUTE_NORMAL,
614 NULL);
615 if (hFile == INVALID_HANDLE_VALUE)
616 {
617 ConErrPrintf ("Can't redirect to file %s\n", err);
618 return;
619 }
620 }
621 if (!SetStdHandle (STD_ERROR_HANDLE, hFile))
622 {
623 ConErrPrintf ("Can't redirect to file %s\n", err);
624 return;
625 }
626
627 if (nRedirFlags & ERROR_APPEND)
628 {
629 LONG lHighPos = 0;
630
631 if (GetFileType (hFile) == FILE_TYPE_DISK)
632 SetFilePointer (hFile, 0, &lHighPos, FILE_END);
633 }
634 #ifdef _DEBUG
635 DebugPrintf (_T("Error redirected to: %s\n"), err);
636 #endif
637 }
638 else if (hOldConErr != INVALID_HANDLE_VALUE)
639 {
640 /* Restore original stderr */
641 HANDLE hErr = GetStdHandle (STD_ERROR_HANDLE);
642 SetStdHandle (STD_ERROR_HANDLE, hOldConErr);
643 if (hOldConErr != hErr)
644 CloseHandle (hErr);
645 hOldConErr = INVALID_HANDLE_VALUE;
646 }
647 #endif
648
649 /* process final command */
650 DoCommand (s);
651
652 #ifdef FEATURE_REDIRECTION
653 /* close old stdin file */
654 #if 0 /* buggy implementation */
655 SetStdHandle (STD_INPUT_HANDLE, hOldConIn);
656 if ((hFile[0] != INVALID_HANDLE_VALUE) &&
657 (hFile[0] != hOldConIn))
658 {
659 /* delete old stdin file, if it is a real file */
660 CloseHandle (hFile[0]);
661 hFile[0] = INVALID_HANDLE_VALUE;
662 DeleteFile (szFileName[0]);
663 *szFileName[0] = _T('\0');
664 }
665
666 /* Restore original STDIN */
667 if (hOldConIn != INVALID_HANDLE_VALUE)
668 {
669 HANDLE hIn = GetStdHandle (STD_INPUT_HANDLE);
670 SetStdHandle (STD_INPUT_HANDLE, hOldConIn);
671 if (hOldConIn != hIn)
672 CloseHandle (hIn);
673 hOldConIn = INVALID_HANDLE_VALUE;
674 }
675 else
676 {
677 #ifdef _DEBUG
678 DebugPrintf (_T("Can't restore STDIN! Is invalid!!\n"), out);
679 #endif
680 }
681 #endif /* buggy implementation */
682
683
684 if (hOldConIn != INVALID_HANDLE_VALUE)
685 {
686 HANDLE hIn = GetStdHandle (STD_INPUT_HANDLE);
687 SetStdHandle (STD_INPUT_HANDLE, hOldConIn);
688 if (hIn == INVALID_HANDLE_VALUE)
689 {
690 #ifdef _DEBUG
691 DebugPrintf (_T("Previous STDIN is invalid!!\n"));
692 #endif
693 }
694 else
695 {
696 if (GetFileType (hIn) == FILE_TYPE_DISK)
697 {
698 if (hFile[0] == hIn)
699 {
700 CloseHandle (hFile[0]);
701 hFile[0] = INVALID_HANDLE_VALUE;
702 DeleteFile (szFileName[0]);
703 *szFileName[0] = _T('\0');
704 }
705 else
706 {
707 #ifdef _DEBUG
708 DebugPrintf (_T("hFile[0] and hIn dont match!!!\n"));
709 #endif
710 }
711 }
712 }
713 }
714
715
716 /* Restore original STDOUT */
717 if (hOldConOut != INVALID_HANDLE_VALUE)
718 {
719 HANDLE hOut = GetStdHandle (STD_OUTPUT_HANDLE);
720 SetStdHandle (STD_OUTPUT_HANDLE, hOldConOut);
721 if (hOldConOut != hOut)
722 CloseHandle (hOut);
723 hOldConOut = INVALID_HANDLE_VALUE;
724 }
725
726 /* Restore original STDERR */
727 if (hOldConErr != INVALID_HANDLE_VALUE)
728 {
729 HANDLE hErr = GetStdHandle (STD_ERROR_HANDLE);
730 SetStdHandle (STD_ERROR_HANDLE, hOldConErr);
731 if (hOldConErr != hErr)
732 CloseHandle (hErr);
733 hOldConErr = INVALID_HANDLE_VALUE;
734 }
735 #endif /* FEATURE_REDIRECTION */
736 }
737
738
739 /*
740 * do the prompt/input/process loop
741 *
742 */
743
744 static INT
745 ProcessInput (BOOL bFlag)
746 {
747 TCHAR commandline[CMDLINE_LENGTH];
748 TCHAR readline[CMDLINE_LENGTH];
749 LPTSTR tp;
750 LPTSTR ip;
751 LPTSTR cp;
752 BOOL bEchoThisLine;
753
754 do
755 {
756 /* if no batch input then... */
757 if (!(ip = ReadBatchLine (&bEchoThisLine)))
758 {
759 if (bFlag)
760 return 0;
761
762 ReadCommand (readline, CMDLINE_LENGTH);
763 ip = readline;
764 bEchoThisLine = FALSE;
765 }
766
767 cp = commandline;
768 while (*ip)
769 {
770 if (*ip == _T('%'))
771 {
772 switch (*++ip)
773 {
774 case _T('%'):
775 *cp++ = *ip++;
776 break;
777
778 case _T('0'):
779 case _T('1'):
780 case _T('2'):
781 case _T('3'):
782 case _T('4'):
783 case _T('5'):
784 case _T('6'):
785 case _T('7'):
786 case _T('8'):
787 case _T('9'):
788 if ((tp = FindArg (*ip - _T('0'))))
789 {
790 cp = stpcpy (cp, tp);
791 ip++;
792 }
793 else
794 *cp++ = _T('%');
795 break;
796
797 case _T('?'):
798 cp += _stprintf (cp, _T("%u"), nErrorLevel);
799 ip++;
800 break;
801
802 default:
803 if ((tp = _tcschr (ip, _T('%'))) && (tp<=(unsigned int)strchr(ip,_T(' '))-1))
804 {
805 char evar[512];
806 *tp = _T('\0');
807
808 /* FIXME: This is just a quick hack!! */
809 /* Do a proper memory allocation!! */
810 if (GetEnvironmentVariable (ip, evar, 512))
811 cp = stpcpy (cp, evar);
812
813 ip = tp + 1;
814 }
815 else
816 {
817 *cp++ = _T('%');
818 }
819 break;
820 }
821 continue;
822 }
823
824 if (_istcntrl (*ip))
825 *ip = _T(' ');
826 *cp++ = *ip++;
827 }
828
829 *cp = _T('\0');
830
831 /* strip trailing spaces */
832 while ((--cp >= commandline) && _istspace (*cp));
833
834 *(cp + 1) = _T('\0');
835
836 /* JPP 19980807 */
837 /* Echo batch file line */
838 if (bEchoThisLine)
839 {
840 PrintPrompt ();
841 ConOutPuts (commandline);
842 }
843
844 if (*commandline)
845 {
846 ParseCommandLine (commandline);
847 if (bEcho && !bIgnoreEcho)
848 ConOutChar ('\n');
849 bIgnoreEcho = FALSE;
850 }
851 }
852 while (!bCanExit || !bExit);
853
854 return 0;
855 }
856
857
858 /*
859 * control-break handler.
860 */
861 BOOL BreakHandler (DWORD dwCtrlType)
862 {
863 if ((dwCtrlType != CTRL_C_EVENT) &&
864 (dwCtrlType != CTRL_BREAK_EVENT))
865 return FALSE;
866
867 if (bChildProcessRunning == TRUE)
868 {
869 GenerateConsoleCtrlEvent (CTRL_C_EVENT,
870 dwChildProcessId);
871 return TRUE;
872 }
873
874 /* FIXME: Handle batch files */
875
876 /* FIXME: Print "^C" */
877
878
879 return TRUE;
880 }
881
882
883 VOID AddBreakHandler (VOID)
884 {
885 #ifndef __REACTOS__
886 SetConsoleCtrlHandler ((PHANDLER_ROUTINE)&BreakHandler,
887 TRUE);
888 #endif
889 }
890
891
892 VOID RemoveBreakHandler (VOID)
893 {
894 #ifndef __REACTOS__
895 SetConsoleCtrlHandler (NULL, FALSE);
896 #endif
897 }
898
899
900 /*
901 * show commands and options that are available.
902 *
903 */
904 static VOID
905 ShowCommands (VOID)
906 {
907 /* print command list */
908 ConOutPrintf (_T("\nInternal commands available:\n"));
909 PrintCommandList ();
910
911 /* print feature list */
912 ConOutPuts ("\nFeatures available:");
913 #ifdef FEATURE_ALIASES
914 ConOutPuts (" [aliases]");
915 #endif
916 #ifdef FEATURE_HISTORY
917 ConOutPuts (" [history]");
918 #endif
919 #ifdef FEATURE_UNIX_FILENAME_COMPLETION
920 ConOutPuts (" [unix filename completion]");
921 #endif
922 #ifdef FEATURE_DIRECTORY_STACK
923 ConOutPuts (" [directory stack]");
924 #endif
925 #ifdef FEATURE_REDIRECTION
926 ConOutPuts (" [redirections and piping]");
927 #endif
928 ConOutChar ('\n');
929 }
930
931
932 /*
933 * set up global initializations and process parameters
934 *
935 * argc - number of parameters to command.com
936 * argv - command-line parameters
937 *
938 */
939 static VOID
940 Initialize (int argc, char *argv[])
941 {
942 TCHAR commandline[CMDLINE_LENGTH];
943 INT i;
944
945 #ifdef _DEBUG
946 INT x;
947
948 DebugPrintf ("[command args:\n");
949 for (x = 0; x < argc; x++)
950 {
951 DebugPrintf ("%d. %s\n", x, argv[x]);
952 }
953 DebugPrintf ("]\n");
954 #endif
955
956 /* get version information */
957 osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
958 GetVersionEx (&osvi);
959
960 InitLocale ();
961
962 /* get default input and output console handles */
963 hOut = GetStdHandle (STD_OUTPUT_HANDLE);
964 hIn = GetStdHandle (STD_INPUT_HANDLE);
965
966 SetConsoleMode (hIn, ENABLE_PROCESSED_INPUT);
967
968 if (argc >= 2 && !_tcsncmp (argv[1], _T("/?"), 2))
969 {
970 ConOutPuts (_T("Starts a new instance of the ReactOS command line interpreter.\n"
971 "\n"
972 "CMD [/[C|K] command][/P][/Q][/T:bf]\n"
973 "\n"
974 " /C command Runs the specified command and terminates.\n"
975 " /K command Runs the specified command and remains.\n"
976 " /P CMD becomes permanent and runs autoexec.bat\n"
977 " (cannot be terminated).\n"
978 " /T:bf Sets the background/foreground color (see COLOR command)."));
979 ExitProcess (0);
980 }
981
982 #ifdef INCLUDE_CMD_CHDIR
983 InitLastPath ();
984 #endif
985
986 #ifdef FATURE_ALIASES
987 InitializeAlias ();
988 #endif
989
990 if (argc >= 2)
991 {
992 for (i = 1; i < argc; i++)
993 {
994 if (!_tcsicmp (argv[i], _T("/p")))
995 {
996 if (!IsValidFileName (_T("\\autoexec.bat")))
997 {
998 #ifdef INCLUDE_CMD_DATE
999 cmd_date ("", "");
1000 #endif
1001 #ifdef INCLUDE_CMD_TIME
1002 cmd_time ("", "");
1003 #endif
1004 }
1005 else
1006 {
1007 ParseCommandLine (_T("\\autoexec.bat"));
1008 }
1009 bCanExit = FALSE;
1010 }
1011 else if (!_tcsicmp (argv[i], _T("/c")))
1012 {
1013 /* This just runs a program and exits */
1014 ++i;
1015 if (argv[i])
1016 {
1017 _tcscpy (commandline, argv[i]);
1018 while (argv[++i])
1019 {
1020 _tcscat (commandline, " ");
1021 _tcscat (commandline, argv[i]);
1022 }
1023
1024 ParseCommandLine(commandline);
1025 ExitProcess (ProcessInput (TRUE));
1026 }
1027 else
1028 {
1029 ExitProcess (0);
1030 }
1031 }
1032 else if (!_tcsicmp (argv[i], _T("/k")))
1033 {
1034 /* This just runs a program and remains */
1035 ++i;
1036 if (argv[i])
1037 {
1038 _tcscpy (commandline, argv[i]);
1039 while (argv[++i])
1040 {
1041 _tcscat (commandline, " ");
1042 _tcscat (commandline, argv[i]);
1043 }
1044
1045 ParseCommandLine(commandline);
1046 }
1047 }
1048 #ifdef INCLUDE_CMD_COLOR
1049 else if (!_tcsnicmp (argv[i], _T("/t:"), 3))
1050 {
1051 /* process /t (color) argument */
1052 wDefColor = (WORD)strtoul (&argv[i][3], NULL, 16);
1053 wColor = wDefColor;
1054 SetScreenColor (wColor, TRUE);
1055 }
1056 #endif
1057 }
1058 }
1059
1060 ShortVersion ();
1061 ShowCommands ();
1062
1063 /* run cmdstart.bat */
1064 if (IsValidFileName (_T("cmdstart.bat")))
1065 {
1066 ParseCommandLine (_T("cmdstart.bat"));
1067 }
1068 else if (IsValidFileName (_T("\\cmdstart.bat")))
1069 {
1070 ParseCommandLine (_T("\\cmdstart.bat"));
1071 }
1072 #ifndef __REACTOS__
1073 else
1074 {
1075 /* try to run cmdstart.bat from install dir */
1076 LPTSTR p;
1077
1078 _tcscpy (commandline, argv[0]);
1079 p = _tcsrchr (commandline, _T('\\')) + 1;
1080 _tcscpy (p, _T("cmdstart.bat"));
1081
1082 if (IsValidFileName (_T("commandline")))
1083 {
1084 ConErrPrintf ("Running %s...\n", commandline);
1085 ParseCommandLine (commandline);
1086 }
1087 }
1088 #endif
1089
1090 #ifdef FEATURE_DIR_STACK
1091 /* initialize directory stack */
1092 InitDirectoryStack ();
1093 #endif
1094
1095
1096 #ifdef FEATURE_HISTORY
1097 /*initialize history*/
1098 InitHistory();
1099 #endif
1100
1101 /* Set COMSPEC environment variable */
1102 #ifndef __REACTOS__
1103 if (argv)
1104 SetEnvironmentVariable (_T("COMSPEC"), argv[0]);
1105 #endif
1106
1107 /* add ctrl break handler */
1108 AddBreakHandler ();
1109 }
1110
1111
1112 static VOID Cleanup (int argc, char *argv[])
1113 {
1114 /* run cmdexit.bat */
1115 if (IsValidFileName (_T("cmdexit.bat")))
1116 {
1117 ConErrPrintf ("Running cmdexit.bat...\n");
1118 ParseCommandLine (_T("cmdexit.bat"));
1119 }
1120 else if (IsValidFileName (_T("\\cmdexit.bat")))
1121 {
1122 ConErrPrintf ("Running \\cmdexit.bat...\n");
1123 ParseCommandLine (_T("\\cmdexit.bat"));
1124 }
1125 #ifndef __REACTOS__
1126 else
1127 {
1128 /* try to run cmdexit.bat from install dir */
1129 TCHAR commandline[CMDLINE_LENGTH];
1130 LPTSTR p;
1131
1132 _tcscpy (commandline, argv[0]);
1133 p = _tcsrchr (commandline, _T('\\')) + 1;
1134 _tcscpy (p, _T("cmdexit.bat"));
1135
1136 if (IsValidFileName (_T("commandline")))
1137 {
1138 ConErrPrintf ("Running %s...\n", commandline);
1139 ParseCommandLine (commandline);
1140 }
1141 }
1142 #endif
1143
1144 #ifdef FEATURE_ALIASES
1145 DestroyAlias ();
1146 #endif
1147
1148 #ifdef FEATURE_DIECTORY_STACK
1149 /* destroy directory stack */
1150 DestroyDirectoryStack ();
1151 #endif
1152
1153 #ifdef INCLUDE_CMD_CHDIR
1154 FreeLastPath ();
1155 #endif
1156
1157 #ifdef FEATURE_HISTORY
1158 CleanHistory();
1159 #endif
1160
1161
1162 /* remove ctrl break handler */
1163 RemoveBreakHandler ();
1164 }
1165
1166
1167 /*
1168 * main function
1169 */
1170 int main (int argc, char *argv[])
1171 {
1172 INT nExitCode;
1173 CONSOLE_SCREEN_BUFFER_INFO Info;
1174
1175 AllocConsole ();
1176 SetFileApisToOEM ();
1177
1178 if( GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), &Info ) == FALSE )
1179 printf( "GetConsoleScreenBufferInfo: Error: %ld\n", GetLastError() );
1180 wColor = Info.wAttributes;
1181 wDefColor = wColor;
1182 /* check switches on command-line */
1183 Initialize (argc, argv);
1184
1185 /* call prompt routine */
1186 nExitCode = ProcessInput (FALSE);
1187
1188 /* do the cleanup */
1189 Cleanup (argc, argv);
1190 FreeConsole ();
1191
1192 return nExitCode;
1193 }
1194
1195 /* EOF */