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