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