9f2b3a143dc245c15d94bb6115d1850560a00428
[reactos.git] / rosapps / cmd / cmd.c
1 /* $Id: cmd.c,v 1.23 2001/02/03 10:40:19 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
121 #include "config.h"
122
123 #include <windows.h>
124 #include <tchar.h>
125 #include <string.h>
126 #include <stdlib.h>
127 #include <ctype.h>
128 #include <stdio.h>
129
130 #include "cmd.h"
131 #include "batch.h"
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 [CMDLINE_LENGTH];
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 /* print command list */
902 ConOutPrintf (_T("\nInternal commands available:\n"));
903 PrintCommandList ();
904
905 /* print feature list */
906 ConOutPuts ("\nFeatures available:");
907 #ifdef FEATURE_ALIASES
908 ConOutPuts (" [aliases]");
909 #endif
910 #ifdef FEATURE_HISTORY
911 ConOutPuts (" [history]");
912 #endif
913 #ifdef FEATURE_UNIX_FILENAME_COMPLETION
914 ConOutPuts (" [unix filename completion]");
915 #endif
916 #ifdef FEATURE_DIRECTORY_STACK
917 ConOutPuts (" [directory stack]");
918 #endif
919 #ifdef FEATURE_REDIRECTION
920 ConOutPuts (" [redirections and piping]");
921 #endif
922 ConOutChar ('\n');
923 }
924
925
926 /*
927 * set up global initializations and process parameters
928 *
929 * argc - number of parameters to command.com
930 * argv - command-line parameters
931 *
932 */
933 static VOID
934 Initialize (int argc, char *argv[])
935 {
936 TCHAR commandline[CMDLINE_LENGTH];
937 INT i;
938
939 #ifdef _DEBUG
940 INT x;
941
942 DebugPrintf ("[command args:\n");
943 for (x = 0; x < argc; x++)
944 {
945 DebugPrintf ("%d. %s\n", x, argv[x]);
946 }
947 DebugPrintf ("]\n");
948 #endif
949
950 /* get version information */
951 osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
952 GetVersionEx (&osvi);
953
954 InitLocale ();
955
956 /* get default input and output console handles */
957 hOut = GetStdHandle (STD_OUTPUT_HANDLE);
958 hIn = GetStdHandle (STD_INPUT_HANDLE);
959
960 SetConsoleMode (hIn, ENABLE_PROCESSED_INPUT);
961
962 if (argc >= 2 && !_tcsncmp (argv[1], _T("/?"), 2))
963 {
964 ConOutPuts (_T("Starts a new instance of the ReactOS command line interpreter.\n"
965 "\n"
966 "CMD [/[C|K] command][/P][/Q][/T:bf]\n"
967 "\n"
968 " /C command Runs the specified command and terminates.\n"
969 " /K command Runs the specified command and remains.\n"
970 " /P CMD becomes permanent and runs autoexec.bat\n"
971 " (cannot be terminated).\n"
972 " /T:bf Sets the background/foreground color (see COLOR command)."));
973 ExitProcess (0);
974 }
975
976 #ifdef INCLUDE_CMD_CHDIR
977 InitLastPath ();
978 #endif
979
980 #ifdef FATURE_ALIASES
981 InitializeAlias ();
982 #endif
983
984 if (argc >= 2)
985 {
986 for (i = 1; i < argc; i++)
987 {
988 if (!_tcsicmp (argv[i], _T("/p")))
989 {
990 if (!IsValidFileName (_T("\\autoexec.bat")))
991 {
992 #ifdef INCLUDE_CMD_DATE
993 cmd_date ("", "");
994 #endif
995 #ifdef INCLUDE_CMD_TIME
996 cmd_time ("", "");
997 #endif
998 }
999 else
1000 {
1001 ParseCommandLine (_T("\\autoexec.bat"));
1002 }
1003 bCanExit = FALSE;
1004 }
1005 else if (!_tcsicmp (argv[i], _T("/c")))
1006 {
1007 /* This just runs a program and exits */
1008 ++i;
1009 if (argv[i])
1010 {
1011 _tcscpy (commandline, argv[i]);
1012 while (argv[++i])
1013 {
1014 _tcscat (commandline, " ");
1015 _tcscat (commandline, argv[i]);
1016 }
1017
1018 ParseCommandLine(commandline);
1019 ExitProcess (ProcessInput (TRUE));
1020 }
1021 else
1022 {
1023 ExitProcess (0);
1024 }
1025 }
1026 else if (!_tcsicmp (argv[i], _T("/k")))
1027 {
1028 /* This just runs a program and remains */
1029 ++i;
1030 if (argv[i])
1031 {
1032 _tcscpy (commandline, argv[i]);
1033 while (argv[++i])
1034 {
1035 _tcscat (commandline, " ");
1036 _tcscat (commandline, argv[i]);
1037 }
1038
1039 ParseCommandLine(commandline);
1040 }
1041 }
1042 #ifdef INCLUDE_CMD_COLOR
1043 else if (!_tcsnicmp (argv[i], _T("/t:"), 3))
1044 {
1045 /* process /t (color) argument */
1046 wDefColor = (WORD)strtoul (&argv[i][3], NULL, 16);
1047 wColor = wDefColor;
1048 SetScreenColor (wColor, TRUE);
1049 }
1050 #endif
1051 }
1052 }
1053
1054 ShortVersion ();
1055 ShowCommands ();
1056
1057 /* run cmdstart.bat */
1058 if (IsValidFileName (_T("cmdstart.bat")))
1059 {
1060 ParseCommandLine (_T("cmdstart.bat"));
1061 }
1062 else if (IsValidFileName (_T("\\cmdstart.bat")))
1063 {
1064 ParseCommandLine (_T("\\cmdstart.bat"));
1065 }
1066 #ifndef __REACTOS__
1067 else
1068 {
1069 /* try to run cmdstart.bat from install dir */
1070 LPTSTR p;
1071
1072 _tcscpy (commandline, argv[0]);
1073 p = _tcsrchr (commandline, _T('\\')) + 1;
1074 _tcscpy (p, _T("cmdstart.bat"));
1075
1076 if (IsValidFileName (_T("commandline")))
1077 {
1078 ConErrPrintf ("Running %s...\n", commandline);
1079 ParseCommandLine (commandline);
1080 }
1081 }
1082 #endif
1083
1084 #ifdef FEATURE_DIR_STACK
1085 /* initialize directory stack */
1086 InitDirectoryStack ();
1087 #endif
1088
1089
1090 #ifdef FEATURE_HISTORY
1091 /*initialize history*/
1092 InitHistory();
1093 #endif
1094
1095 /* Set COMSPEC environment variable */
1096 #ifndef __REACTOS__
1097 if (argv)
1098 SetEnvironmentVariable (_T("COMSPEC"), argv[0]);
1099 #endif
1100
1101 /* add ctrl break handler */
1102 AddBreakHandler ();
1103 }
1104
1105
1106 static VOID Cleanup (int argc, char *argv[])
1107 {
1108 /* run cmdexit.bat */
1109 if (IsValidFileName (_T("cmdexit.bat")))
1110 {
1111 ConErrPrintf ("Running cmdexit.bat...\n");
1112 ParseCommandLine (_T("cmdexit.bat"));
1113 }
1114 else if (IsValidFileName (_T("\\cmdexit.bat")))
1115 {
1116 ConErrPrintf ("Running \\cmdexit.bat...\n");
1117 ParseCommandLine (_T("\\cmdexit.bat"));
1118 }
1119 #ifndef __REACTOS__
1120 else
1121 {
1122 /* try to run cmdexit.bat from install dir */
1123 TCHAR commandline[CMDLINE_LENGTH];
1124 LPTSTR p;
1125
1126 _tcscpy (commandline, argv[0]);
1127 p = _tcsrchr (commandline, _T('\\')) + 1;
1128 _tcscpy (p, _T("cmdexit.bat"));
1129
1130 if (IsValidFileName (_T("commandline")))
1131 {
1132 ConErrPrintf ("Running %s...\n", commandline);
1133 ParseCommandLine (commandline);
1134 }
1135 }
1136 #endif
1137
1138 #ifdef FEATURE_ALIASES
1139 DestroyAlias ();
1140 #endif
1141
1142 #ifdef FEATURE_DIECTORY_STACK
1143 /* destroy directory stack */
1144 DestroyDirectoryStack ();
1145 #endif
1146
1147 #ifdef INCLUDE_CMD_CHDIR
1148 FreeLastPath ();
1149 #endif
1150
1151 #ifdef FEATURE_HISTORY
1152 CleanHistory();
1153 #endif
1154
1155
1156 /* remove ctrl break handler */
1157 RemoveBreakHandler ();
1158 }
1159
1160
1161 /*
1162 * main function
1163 */
1164 int main (int argc, char *argv[])
1165 {
1166 INT nExitCode;
1167 CONSOLE_SCREEN_BUFFER_INFO Info;
1168
1169 AllocConsole ();
1170 SetFileApisToOEM ();
1171
1172 if( GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), &Info ) == FALSE )
1173 printf( "GetConsoleScreenBufferInfo: Error: %ld\n", GetLastError() );
1174 wColor = Info.wAttributes;
1175 wDefColor = wColor;
1176 /* check switches on command-line */
1177 Initialize (argc, argv);
1178
1179 /* call prompt routine */
1180 nExitCode = ProcessInput (FALSE);
1181
1182 /* do the cleanup */
1183 Cleanup (argc, argv);
1184 FreeConsole ();
1185
1186 return nExitCode;
1187 }
1188
1189 /* EOF */