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