update for HEAD-2003050101
[reactos.git] / subsys / system / cmd / cmd.c
diff --git a/subsys/system/cmd/cmd.c b/subsys/system/cmd/cmd.c
new file mode 100644 (file)
index 0000000..54cf665
--- /dev/null
@@ -0,0 +1,1190 @@
+/* $Id$
+ *
+ *  CMD.C - command-line interface.
+ *
+ *
+ *  History:
+ *
+ *    17 Jun 1994 (Tim Norman)
+ *        started.
+ *
+ *    08 Aug 1995 (Matt Rains)
+ *        I have cleaned up the source code. changes now bring this source
+ *        into guidelines for recommended programming practice.
+ *
+ *        A added the the standard FreeDOS GNU licence test to the
+ *        initialize() function.
+ *
+ *        Started to replace puts() with printf(). this will help
+ *        standardize output. please follow my lead.
+ *
+ *        I have added some constants to help making changes easier.
+ *
+ *    15 Dec 1995 (Tim Norman)
+ *        major rewrite of the code to make it more efficient and add
+ *        redirection support (finally!)
+ *
+ *    06 Jan 1996 (Tim Norman)
+ *        finished adding redirection support!  Changed to use our own
+ *        exec code (MUCH thanks to Svante Frey!!)
+ *
+ *    29 Jan 1996 (Tim Norman)
+ *        added support for CHDIR, RMDIR, MKDIR, and ERASE, as per
+ *        suggestion of Steffan Kaiser
+ *
+ *        changed "file not found" error message to "bad command or
+ *        filename" thanks to Dustin Norman for noticing that confusing
+ *        message!
+ *
+ *        changed the format to call internal commands (again) so that if
+ *        they want to split their commands, they can do it themselves 
+ *        (none of the internal functions so far need that much power, anyway)
+ *
+ *    27 Aug 1996 (Tim Norman)
+ *        added in support for Oliver Mueller's ALIAS command
+ *
+ *    14 Jun 1997 (Steffan Kaiser)
+ *        added ctrl-break handling and error level
+ *
+ *    16 Jun 1998 (Rob Lake)
+ *        Runs command.com if /P is specified in command line.  Command.com
+ *        also stays permanent.  If /C is in the command line, starts the
+ *        program next in the line.
+ *
+ *    21 Jun 1998 (Rob Lake)
+ *        Fixed up /C so that arguments for the program
+ *
+ *    08-Jul-1998 (John P. Price)
+ *        Now sets COMSPEC environment variable
+ *        misc clean up and optimization
+ *        added date and time commands
+ *        changed to using spawnl instead of exec.  exec does not copy the
+ *        environment to the child process!
+ *
+ *    14 Jul 1998 (Hans B Pufal)
+ *        Reorganised source to be more efficient and to more closely
+ *        follow MS-DOS conventions. (eg %..% environment variable
+ *        replacement works form command line as well as batch file.
+ *
+ *        New organisation also properly support nested batch files.
+ *
+ *        New command table structure is half way towards providing a
+ *        system in which COMMAND will find out what internal commands
+ *        are loaded
+ *
+ *    24 Jul 1998 (Hans B Pufal) [HBP_003]
+ *        Fixed return value when called with /C option
+ *
+ *    27 Jul 1998  John P. Price
+ *        added config.h include
+ *
+ *    28 Jul 1998  John P. Price
+ *        added showcmds function to show commands and options available
+ *
+ *    07-Aug-1998 (John P Price <linux-guru@gcfl.net>)
+ *        Fixed carrage return output to better match MSDOS with echo
+ *        on or off. (marked with "JPP 19980708")
+ *
+ *    07-Dec-1998 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
+ *        First ReactOS release.
+ *        Extended length of commandline buffers to 512.
+ *
+ *    13-Dec-1998 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
+ *        Added COMSPEC environment variable.
+ *        Added "/t" support (color) on cmd command line.
+ *
+ *    07-Jan-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
+ *        Added help text ("cmd /?").
+ *
+ *    25-Jan-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
+ *        Unicode and redirection safe!
+ *        Fixed redirections and piping.
+ *        Piping is based on temporary files, but basic support
+ *        for anonymous pipes already exists.
+ *
+ *    27-Jan-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
+ *        Replaced spawnl() by CreateProcess().
+ *
+ *    22-Oct-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
+ *        Added break handler.
+ *
+ *    15-Dec-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
+ *        Fixed current directory
+ *
+ *    28-Dec-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
+ *        Restore window title after program/batch execution
+ *
+ *    03-Feb-2001 (Eric Kohl <ekohl@rz-online.de>)
+ *        Workaround because argc[0] is NULL under ReactOS
+ *
+ *    23-Feb-2001 (Carl Nettelblad <cnettel@hem.passagen.se>)
+ *        %envvar% replacement conflicted with for.
+ */
+
+#include "config.h"
+
+#include <windows.h>
+#include <tchar.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdio.h>
+
+#include "cmd.h"
+#include "batch.h"
+
+
+BOOL bExit = FALSE;       /* indicates EXIT was typed */
+BOOL bCanExit = TRUE;     /* indicates if this shell is exitable */
+BOOL bCtrlBreak = FALSE;  /* Ctrl-Break or Ctrl-C hit */
+BOOL bIgnoreEcho = FALSE; /* Ignore 'newline' before 'cls' */
+INT  nErrorLevel = 0;     /* Errorlevel of last launched external program */
+BOOL bChildProcessRunning = FALSE;
+DWORD dwChildProcessId = 0;
+OSVERSIONINFO osvi;
+HANDLE hIn;
+HANDLE hOut;
+
+#ifdef INCLUDE_CMD_COLOR
+WORD wColor;              /* current color */
+WORD wDefColor;           /* default color */
+#endif
+
+
+/*
+ *  is character a delimeter when used on first word?
+ *
+ */
+
+static BOOL IsDelimiter (TCHAR c)
+{
+       return (c == _T('/') || c == _T('=') || c == _T('\0') || _istspace (c));
+}
+
+
+/*
+ * This command (in first) was not found in the command table
+ *
+ * first - first word on command line
+ * rest  - rest of command line
+ */
+
+static VOID
+Execute (LPTSTR first, LPTSTR rest)
+{
+       TCHAR szFullName[MAX_PATH];
+#ifndef __REACTOS__
+       TCHAR szWindowTitle[MAX_PATH];
+#endif
+       DWORD dwExitCode = 0;
+
+#ifdef _DEBUG
+       DebugPrintf ("Execute: \'%s\' \'%s\'\n", first, rest);
+#endif
+
+       /* check for a drive change */
+       if ((_istalpha (first[0])) && (!_tcscmp (first + 1, _T(":"))))
+       {       
+               BOOL working = TRUE;
+               if (!SetCurrentDirectory(first))
+               /* Guess they changed disc or something, handle that gracefully and get to root */
+               {
+                       TCHAR str[4];
+                       str[0]=first[0];
+                       str[1]=':';
+                       str[2]='\\';
+                       str[3]=0;
+                       working = SetCurrentDirectory(str);
+               }
+
+               if (!working) ConErrPuts (INVALIDDRIVE);
+
+               return;
+       }
+
+       /* get the PATH environment variable and parse it */
+       /* search the PATH environment variable for the binary */
+       if (!SearchForExecutable (first, szFullName))
+       {
+               error_bad_command ();
+               return;
+       }
+
+#ifndef __REACTOS__
+       GetConsoleTitle (szWindowTitle, MAX_PATH);
+#endif
+
+       /* check if this is a .BAT or .CMD file */
+       if (!_tcsicmp (_tcsrchr (szFullName, _T('.')), _T(".bat")) ||
+               !_tcsicmp (_tcsrchr (szFullName, _T('.')), _T(".cmd")))
+       {
+#ifdef _DEBUG
+               DebugPrintf ("[BATCH: %s %s]\n", szFullName, rest);
+#endif
+               Batch (szFullName, first, rest);
+       }
+       else
+       {
+               /* exec the program */
+               TCHAR szFullCmdLine [CMDLINE_LENGTH];
+               PROCESS_INFORMATION prci;
+               STARTUPINFO stui;
+
+#ifdef _DEBUG
+               DebugPrintf ("[EXEC: %s %s]\n", szFullName, rest);
+#endif
+               /* build command line for CreateProcess() */
+               _tcscpy (szFullCmdLine, first);
+               _tcscat (szFullCmdLine, _T(" "));
+               _tcscat (szFullCmdLine, rest);
+
+               /* fill startup info */
+               memset (&stui, 0, sizeof (STARTUPINFO));
+               stui.cb = sizeof (STARTUPINFO);
+               stui.dwFlags = STARTF_USESHOWWINDOW;
+               stui.wShowWindow = SW_SHOWDEFAULT;
+
+               // return console to standard mode
+               SetConsoleMode (GetStdHandle(STD_INPUT_HANDLE),
+                               ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT );
+               
+               if (CreateProcess (szFullName,
+                                  szFullCmdLine,
+                                  NULL,
+                                  NULL,
+                                  FALSE,
+                                  CREATE_NEW_PROCESS_GROUP,
+                                  NULL,
+                                  NULL,
+                                  &stui,
+                                  &prci))
+               {
+                       /* FIXME: Protect this with critical section */
+                       bChildProcessRunning = TRUE;
+                       dwChildProcessId = prci.dwProcessId;
+
+                       WaitForSingleObject (prci.hProcess, INFINITE);
+
+                       /* FIXME: Protect this with critical section */
+                       bChildProcessRunning = TRUE;
+
+                       GetExitCodeProcess (prci.hProcess, &dwExitCode);
+                       nErrorLevel = (INT)dwExitCode;
+                       CloseHandle (prci.hThread);
+                       CloseHandle (prci.hProcess);
+               }
+               else
+               {
+                       ErrorMessage (GetLastError (),
+                                     "Error executing CreateProcess()!!\n");
+               }
+               // restore console mode
+               SetConsoleMode( GetStdHandle( STD_INPUT_HANDLE ),
+                               ENABLE_PROCESSED_INPUT );
+       }
+
+#ifndef __REACTOS__
+       SetConsoleTitle (szWindowTitle);
+#endif
+}
+
+
+/*
+ * look through the internal commands and determine whether or not this
+ * command is one of them.  If it is, call the command.  If not, call
+ * execute to run it as an external program.
+ *
+ * line - the command line of the program to run
+ *
+ */
+
+static VOID
+DoCommand (LPTSTR line)
+{
+       TCHAR com[MAX_PATH];  /* the first word in the command */
+       LPTSTR cp = com;
+       LPTSTR cstart;
+       LPTSTR rest = line;   /* pointer to the rest of the command line */
+       INT cl;
+       LPCOMMAND cmdptr;
+
+#ifdef _DEBUG
+       DebugPrintf ("DoCommand: (\'%s\')\n", line);
+#endif /* DEBUG */
+
+       /* Skip over initial white space */
+       while (isspace (*rest))
+               rest++;
+
+       cstart = rest;
+
+       /* Anything to do ? */
+       if (*rest)
+       {
+               if (*rest == _T('"'))
+               {
+                       /* treat quoted words specially */
+
+                       rest++;
+
+                       while(*rest != _T('\0') && *rest != _T('"'))
+                               *cp++ = _totlower (*rest++);
+               }
+               else
+               {
+                       while (!IsDelimiter (*rest))
+                               *cp++ = _totlower (*rest++);
+               }
+
+
+               /* Terminate first word */
+               *cp = _T('\0');
+
+               /* Skip over whitespace to rest of line */
+               while (_istspace (*rest))
+                       rest++;
+
+               /* Scan internal command table */
+               for (cmdptr = cmds;; cmdptr++)
+               {
+                       /* If end of table execute ext cmd */
+                       if (cmdptr->name == NULL)
+                       {
+                               Execute (com, rest);
+                               break;
+                       }
+
+                       if (!_tcscmp (com, cmdptr->name))
+                       {
+                               cmdptr->func (com, rest);
+                               break;
+                       }
+
+                       /* The following code handles the case of commands like CD which
+                        * are recognised even when the command name and parameter are
+                        * not space separated.
+                        *
+                        * e.g dir..
+                        * cd\freda
+                        */
+
+                       /* Get length of command name */
+                       cl = _tcslen (cmdptr->name);
+
+                       if ((cmdptr->flags & CMD_SPECIAL) &&
+                           (!_tcsncmp (cmdptr->name, com, cl)) &&
+                           (_tcschr (_T("\\.-"), *(com + cl))))
+                       {
+                               /* OK its one of the specials...*/
+
+                               /* Terminate first word properly */
+                               com[cl] = _T('\0');
+
+                               /* Call with new rest */
+                               cmdptr->func (com, cstart + cl);
+                               break;
+                       }
+               }
+       }
+}
+
+
+/*
+ * process the command line and execute the appropriate functions
+ * full input/output redirection and piping are supported
+ */
+
+VOID ParseCommandLine (LPTSTR cmd)
+{
+       TCHAR cmdline[CMDLINE_LENGTH];
+       LPTSTR s;
+#ifdef FEATURE_REDIRECTION
+       TCHAR in[CMDLINE_LENGTH] = "";
+       TCHAR out[CMDLINE_LENGTH] = "";
+       TCHAR err[CMDLINE_LENGTH] = "";
+       TCHAR szTempPath[MAX_PATH] = _T(".\\");
+       TCHAR szFileName[2][MAX_PATH] = {"", ""};
+       HANDLE hFile[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
+       LPTSTR t = NULL;
+       INT  num = 0;
+       INT  nRedirFlags = 0;
+
+       HANDLE hOldConIn;
+       HANDLE hOldConOut;
+       HANDLE hOldConErr;
+#endif /* FEATURE_REDIRECTION */
+
+       _tcscpy (cmdline, cmd);
+       s = &cmdline[0];
+
+#ifdef _DEBUG
+       DebugPrintf ("ParseCommandLine: (\'%s\')\n", s);
+#endif /* DEBUG */
+
+#ifdef FEATURE_ALIASES
+       /* expand all aliases */
+       ExpandAlias (s, CMDLINE_LENGTH);
+#endif /* FEATURE_ALIAS */
+
+#ifdef FEATURE_REDIRECTION
+       /* find the temp path to store temporary files */
+       GetTempPath (MAX_PATH, szTempPath);
+       if (szTempPath[_tcslen (szTempPath) - 1] != _T('\\'))
+               _tcscat (szTempPath, _T("\\"));
+
+       /* get the redirections from the command line */
+       num = GetRedirection (s, in, out, err, &nRedirFlags);
+
+       /* more efficient, but do we really need to do this? */
+       for (t = in; _istspace (*t); t++)
+               ;
+       _tcscpy (in, t);
+
+       for (t = out; _istspace (*t); t++)
+               ;
+       _tcscpy (out, t);
+
+       for (t = err; _istspace (*t); t++)
+               ;
+       _tcscpy (err, t);
+
+       /* Set up the initial conditions ... */
+       /* preserve STDIN, STDOUT and STDERR handles */
+       hOldConIn  = GetStdHandle (STD_INPUT_HANDLE);
+       hOldConOut = GetStdHandle (STD_OUTPUT_HANDLE);
+       hOldConErr = GetStdHandle (STD_ERROR_HANDLE);
+
+       /* redirect STDIN */
+       if (in[0])
+       {
+               HANDLE hFile;
+               SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
+
+               hFile = CreateFile (in, GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
+                                   FILE_ATTRIBUTE_NORMAL, NULL);
+               if (hFile == INVALID_HANDLE_VALUE)
+               {
+                       ConErrPrintf ("Can't redirect input from file %s\n", in);
+                       return;
+               }
+
+               if (!SetStdHandle (STD_INPUT_HANDLE, hFile))
+               {
+                       ConErrPrintf ("Can't redirect input from file %s\n", in);
+                       return;
+               }
+#ifdef _DEBUG
+               DebugPrintf (_T("Input redirected from: %s\n"), in);
+#endif
+       }
+
+       /* Now do all but the last pipe command */
+       *szFileName[0] = '\0';
+       hFile[0] = INVALID_HANDLE_VALUE;
+
+       while (num-- > 1)
+       {
+               SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
+
+               /* Create unique temporary file name */
+               GetTempFileName (szTempPath, "CMD", 0, szFileName[1]);
+
+               /* Set current stdout to temporary file */
+               hFile[1] = CreateFile (szFileName[1], GENERIC_WRITE, 0, &sa,
+                                      TRUNCATE_EXISTING, FILE_ATTRIBUTE_TEMPORARY, NULL);
+               SetStdHandle (STD_OUTPUT_HANDLE, hFile[1]);
+
+               DoCommand (s);
+
+               /* close stdout file */
+               SetStdHandle (STD_OUTPUT_HANDLE, hOldConOut);
+               if ((hFile[1] != INVALID_HANDLE_VALUE) && (hFile[1] != hOldConOut))
+               {
+                       CloseHandle (hFile[1]);
+                       hFile[1] = INVALID_HANDLE_VALUE;
+               }
+
+               /* close old stdin file */
+               SetStdHandle (STD_INPUT_HANDLE, hOldConIn);
+               if ((hFile[0] != INVALID_HANDLE_VALUE) && (hFile[0] != hOldConIn))
+               {
+                       /* delete old stdin file, if it is a real file */
+                       CloseHandle (hFile[0]);
+                       hFile[0] = INVALID_HANDLE_VALUE;
+                       DeleteFile (szFileName[0]);
+                       *szFileName[0] = _T('\0');
+               }
+
+               /* copy stdout file name to stdin file name */
+               _tcscpy (szFileName[0], szFileName[1]);
+               *szFileName[1] = _T('\0');
+
+               /* open new stdin file */
+               hFile[0] = CreateFile (szFileName[0], GENERIC_READ, 0, &sa,
+                                      OPEN_EXISTING, FILE_ATTRIBUTE_TEMPORARY, NULL);
+               SetStdHandle (STD_INPUT_HANDLE, hFile[0]);
+
+               s = s + _tcslen (s) + 1;
+       }
+
+       /* Now set up the end conditions... */
+       /* redirect STDOUT */
+       if (out[0])
+       {
+               /* Final output to here */
+               HANDLE hFile;
+               SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
+
+               hFile = CreateFile (out, GENERIC_WRITE, FILE_SHARE_READ, &sa,
+                                   (nRedirFlags & OUTPUT_APPEND) ? OPEN_ALWAYS : CREATE_ALWAYS,
+                                   FILE_ATTRIBUTE_NORMAL, NULL);
+               if (hFile == INVALID_HANDLE_VALUE)
+               {
+                       ConErrPrintf ("Can't redirect to file %s\n", out);
+                       return;
+               }
+
+               if (!SetStdHandle (STD_OUTPUT_HANDLE, hFile))
+               {
+                       ConErrPrintf ("Can't redirect to file %s\n", out);
+                       return;
+               }
+
+               if (nRedirFlags & OUTPUT_APPEND)
+               {
+                       LONG lHighPos = 0;
+
+                       if (GetFileType (hFile) == FILE_TYPE_DISK)
+                               SetFilePointer (hFile, 0, &lHighPos, FILE_END);
+               }
+#ifdef _DEBUG
+               DebugPrintf (_T("Output redirected to: %s\n"), out);
+#endif
+       }
+       else if (hOldConOut != INVALID_HANDLE_VALUE)
+       {
+               /* Restore original stdout */
+               HANDLE hOut = GetStdHandle (STD_OUTPUT_HANDLE);
+               SetStdHandle (STD_OUTPUT_HANDLE, hOldConOut);
+               if (hOldConOut != hOut)
+                       CloseHandle (hOut);
+               hOldConOut = INVALID_HANDLE_VALUE;
+       }
+
+       /* redirect STDERR */
+       if (err[0])
+       {
+               /* Final output to here */
+               HANDLE hFile;
+               SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
+
+               if (!_tcscmp (err, out))
+               {
+#ifdef _DEBUG
+                       DebugPrintf (_T("Stdout and stderr will use the same file!!\n"));
+#endif
+                       DuplicateHandle (GetCurrentProcess (),
+                                        GetStdHandle (STD_OUTPUT_HANDLE),
+                                        GetCurrentProcess (),
+                                        &hFile, 0, TRUE, DUPLICATE_SAME_ACCESS);
+               }
+               else
+               {
+                       hFile = CreateFile (err,
+                                           GENERIC_WRITE,
+                                           0,
+                                           &sa,
+                                           (nRedirFlags & ERROR_APPEND) ? OPEN_ALWAYS : CREATE_ALWAYS,
+                                           FILE_ATTRIBUTE_NORMAL,
+                                           NULL);
+                       if (hFile == INVALID_HANDLE_VALUE)
+                       {
+                               ConErrPrintf ("Can't redirect to file %s\n", err);
+                               return;
+                       }
+               }
+               if (!SetStdHandle (STD_ERROR_HANDLE, hFile))
+               {
+                       ConErrPrintf ("Can't redirect to file %s\n", err);
+                       return;
+               }
+
+               if (nRedirFlags & ERROR_APPEND)
+               {
+                       LONG lHighPos = 0;
+
+                       if (GetFileType (hFile) == FILE_TYPE_DISK)
+                               SetFilePointer (hFile, 0, &lHighPos, FILE_END);
+               }
+#ifdef _DEBUG
+               DebugPrintf (_T("Error redirected to: %s\n"), err);
+#endif
+       }
+       else if (hOldConErr != INVALID_HANDLE_VALUE)
+       {
+               /* Restore original stderr */
+               HANDLE hErr = GetStdHandle (STD_ERROR_HANDLE);
+               SetStdHandle (STD_ERROR_HANDLE, hOldConErr);
+               if (hOldConErr != hErr)
+                       CloseHandle (hErr);
+               hOldConErr = INVALID_HANDLE_VALUE;
+       }
+#endif
+
+       /* process final command */
+       DoCommand (s);
+
+#ifdef FEATURE_REDIRECTION
+       /* close old stdin file */
+#if 0  /* buggy implementation */
+       SetStdHandle (STD_INPUT_HANDLE, hOldConIn);
+       if ((hFile[0] != INVALID_HANDLE_VALUE) && 
+               (hFile[0] != hOldConIn))
+       {
+               /* delete old stdin file, if it is a real file */
+               CloseHandle (hFile[0]);
+               hFile[0] = INVALID_HANDLE_VALUE;
+               DeleteFile (szFileName[0]);
+               *szFileName[0] = _T('\0');
+       }
+
+       /* Restore original STDIN */
+       if (hOldConIn != INVALID_HANDLE_VALUE)
+       {
+               HANDLE hIn = GetStdHandle (STD_INPUT_HANDLE);
+               SetStdHandle (STD_INPUT_HANDLE, hOldConIn);
+               if (hOldConIn != hIn)
+                       CloseHandle (hIn);
+               hOldConIn = INVALID_HANDLE_VALUE;
+       }
+       else
+       {
+#ifdef _DEBUG
+               DebugPrintf (_T("Can't restore STDIN! Is invalid!!\n"), out);
+#endif
+       }
+#endif  /* buggy implementation */
+
+
+       if (hOldConIn != INVALID_HANDLE_VALUE)
+       {
+               HANDLE hIn = GetStdHandle (STD_INPUT_HANDLE);
+               SetStdHandle (STD_INPUT_HANDLE, hOldConIn);
+               if (hIn == INVALID_HANDLE_VALUE)
+               {
+#ifdef _DEBUG
+                       DebugPrintf (_T("Previous STDIN is invalid!!\n"));
+#endif
+               }
+               else
+               {
+                       if (GetFileType (hIn) == FILE_TYPE_DISK)
+                       {
+                               if (hFile[0] == hIn)
+                               {
+                                       CloseHandle (hFile[0]);
+                                       hFile[0] = INVALID_HANDLE_VALUE;
+                                       DeleteFile (szFileName[0]);
+                                       *szFileName[0] = _T('\0');
+                               }
+                               else
+                               {
+#ifdef _DEBUG
+                                       DebugPrintf (_T("hFile[0] and hIn dont match!!!\n"));
+#endif
+                               }
+                       }
+               }
+       }
+
+
+       /* Restore original STDOUT */
+       if (hOldConOut != INVALID_HANDLE_VALUE)
+       {
+               HANDLE hOut = GetStdHandle (STD_OUTPUT_HANDLE);
+               SetStdHandle (STD_OUTPUT_HANDLE, hOldConOut);
+               if (hOldConOut != hOut)
+                       CloseHandle (hOut);
+               hOldConOut = INVALID_HANDLE_VALUE;
+       }
+
+       /* Restore original STDERR */
+       if (hOldConErr != INVALID_HANDLE_VALUE)
+       {
+               HANDLE hErr = GetStdHandle (STD_ERROR_HANDLE);
+               SetStdHandle (STD_ERROR_HANDLE, hOldConErr);
+               if (hOldConErr != hErr)
+                       CloseHandle (hErr);
+               hOldConErr = INVALID_HANDLE_VALUE;
+       }
+#endif /* FEATURE_REDIRECTION */
+}
+
+
+/*
+ * do the prompt/input/process loop
+ *
+ */
+
+static INT 
+ProcessInput (BOOL bFlag)
+{
+       TCHAR commandline[CMDLINE_LENGTH];
+       TCHAR readline[CMDLINE_LENGTH];
+       LPTSTR tp = NULL;
+       LPTSTR ip;
+       LPTSTR cp;
+       BOOL bEchoThisLine;
+
+       do
+       {
+               /* if no batch input then... */
+               if (!(ip = ReadBatchLine (&bEchoThisLine)))
+               {
+                       if (bFlag)
+                               return 0;
+
+                       ReadCommand (readline, CMDLINE_LENGTH);
+                       ip = readline;
+                       bEchoThisLine = FALSE;
+               }
+
+               cp = commandline;
+               while (*ip)
+               {
+                       if (*ip == _T('%'))
+                       {
+                               switch (*++ip)
+                               {
+                                       case _T('%'):
+                                               *cp++ = *ip++;
+                                               break;
+
+                                       case _T('0'):
+                                       case _T('1'):
+                                       case _T('2'):
+                                       case _T('3'):
+                                       case _T('4'):
+                                       case _T('5'):
+                                       case _T('6'):
+                                       case _T('7'):
+                                       case _T('8'):
+                                       case _T('9'):
+                                               if ((tp = FindArg (*ip - _T('0'))))
+                                               {
+                                                       cp = stpcpy (cp, tp);
+                                                       ip++;
+                                               }
+                                               else
+                                                       *cp++ = _T('%');
+                                               break;
+
+                                       case _T('?'):
+                                               cp += _stprintf (cp, _T("%u"), nErrorLevel);
+                                               ip++;
+                                               break;
+
+                                       default:
+                                               tp = _tcschr(ip, _T('%'));
+                                               if ((tp != NULL) &&
+                                                   (tp <= _tcschr(ip, _T(' ')) - 1))
+                                               {
+                                                       char evar[512];
+                                                       *tp = _T('\0');
+
+                                                       /* FIXME: This is just a quick hack!! */
+                                                       /* Do a proper memory allocation!! */
+                                                       if (GetEnvironmentVariable (ip, evar, 512))
+                                                               cp = stpcpy (cp, evar);
+
+                                                       ip = tp + 1;
+                                               }
+                                               else
+                                               {
+                                                       *cp++ = _T('%');
+                                               }
+                                               break;
+                               }
+                               continue;
+                       }
+
+                       if (_istcntrl (*ip))
+                               *ip = _T(' ');
+                       *cp++ = *ip++;
+               }
+
+               *cp = _T('\0');
+
+               /* strip trailing spaces */
+               while ((--cp >= commandline) && _istspace (*cp));
+
+               *(cp + 1) = _T('\0');
+
+               /* JPP 19980807 */
+               /* Echo batch file line */
+               if (bEchoThisLine)
+               {
+                       PrintPrompt ();
+                       ConOutPuts (commandline);
+               }
+
+               if (*commandline)
+               {
+                       ParseCommandLine (commandline);
+                       if (bEcho && !bIgnoreEcho)
+                               ConOutChar ('\n');
+                       bIgnoreEcho = FALSE;
+               }
+       }
+       while (!bCanExit || !bExit);
+
+       return 0;
+}
+
+
+/*
+ * control-break handler.
+ */
+BOOL BreakHandler (DWORD dwCtrlType)
+{
+       if ((dwCtrlType != CTRL_C_EVENT) &&
+           (dwCtrlType != CTRL_BREAK_EVENT))
+               return FALSE;
+
+       if (bChildProcessRunning == TRUE)
+       {
+               GenerateConsoleCtrlEvent (CTRL_C_EVENT,
+                                         dwChildProcessId);
+               return TRUE;
+       }
+
+       /* FIXME: Handle batch files */
+
+       /* FIXME: Print "^C" */
+
+
+       return TRUE;
+}
+
+
+VOID AddBreakHandler (VOID)
+{
+#ifndef __REACTOS__
+       SetConsoleCtrlHandler ((PHANDLER_ROUTINE)&BreakHandler,
+                              TRUE);
+#endif
+}
+
+
+VOID RemoveBreakHandler (VOID)
+{
+#ifndef __REACTOS__
+       SetConsoleCtrlHandler (NULL, FALSE);
+#endif
+}
+
+
+/*
+ * show commands and options that are available.
+ *
+ */
+static VOID
+ShowCommands (VOID)
+{
+       /* print command list */
+       ConOutPrintf (_T("\nInternal commands available:\n"));
+       PrintCommandList ();
+
+       /* print feature list */
+       ConOutPuts ("\nFeatures available:");
+#ifdef FEATURE_ALIASES
+       ConOutPuts ("  [aliases]");
+#endif
+#ifdef FEATURE_HISTORY
+       ConOutPuts ("  [history]");
+#endif
+#ifdef FEATURE_UNIX_FILENAME_COMPLETION
+       ConOutPuts ("  [unix filename completion]");
+#endif
+#ifdef FEATURE_DIRECTORY_STACK
+       ConOutPuts ("  [directory stack]");
+#endif
+#ifdef FEATURE_REDIRECTION
+       ConOutPuts ("  [redirections and piping]");
+#endif
+       ConOutChar ('\n');
+}
+
+
+/*
+ * set up global initializations and process parameters
+ *
+ * argc - number of parameters to command.com
+ * argv - command-line parameters
+ *
+ */
+static VOID
+Initialize (int argc, char *argv[])
+{
+       TCHAR commandline[CMDLINE_LENGTH];
+       TCHAR ModuleName[_MAX_PATH + 1];
+       INT i;
+
+#ifdef _DEBUG
+       INT x;
+
+       DebugPrintf ("[command args:\n");
+       for (x = 0; x < argc; x++)
+       {
+               DebugPrintf ("%d. %s\n", x, argv[x]);
+       }
+       DebugPrintf ("]\n");
+#endif
+
+       /* get version information */
+       osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+       GetVersionEx (&osvi);
+
+       InitLocale ();
+
+       /* get default input and output console handles */
+       hOut = GetStdHandle (STD_OUTPUT_HANDLE);
+       hIn  = GetStdHandle (STD_INPUT_HANDLE);
+
+
+       if (argc >= 2 && !_tcsncmp (argv[1], _T("/?"), 2))
+       {
+               ConOutPuts (_T("Starts a new instance of the ReactOS command line interpreter.\n"
+                              "\n"
+                              "CMD [/[C|K] command][/P][/Q][/T:bf]\n"
+                              "\n"
+                              "  /C command  Runs the specified command and terminates.\n"
+                              "  /K command  Runs the specified command and remains.\n"
+                              "  /P          CMD becomes permanent and runs autoexec.bat\n"
+                              "              (cannot be terminated).\n"
+                              "  /T:bf       Sets the background/foreground color (see COLOR command)."));
+               ExitProcess (0);
+       }
+       SetConsoleMode (hIn, ENABLE_PROCESSED_INPUT);
+
+#ifdef INCLUDE_CMD_CHDIR
+       InitLastPath ();
+#endif
+
+#ifdef FATURE_ALIASES
+       InitializeAlias ();
+#endif
+
+       if (argc >= 2)
+       {
+               for (i = 1; i < argc; i++)
+               {
+                       if (!_tcsicmp (argv[i], _T("/p")))
+                       {
+                               if (!IsValidFileName (_T("\\autoexec.bat")))
+                               {
+#ifdef INCLUDE_CMD_DATE
+                                       cmd_date ("", "");
+#endif
+#ifdef INCLUDE_CMD_TIME
+                                       cmd_time ("", "");
+#endif
+                               }
+                               else
+                               {
+                                       ParseCommandLine (_T("\\autoexec.bat"));
+                               }
+                               bCanExit = FALSE;
+                       }
+                       else if (!_tcsicmp (argv[i], _T("/c")))
+                       {
+                               /* This just runs a program and exits */
+                               ++i;
+                               if (argv[i])
+                               {
+                                       _tcscpy (commandline, argv[i]);
+                                       while (argv[++i])
+                                       {
+                                               _tcscat (commandline, " ");
+                                               _tcscat (commandline, argv[i]);
+                                       }
+
+                                       ParseCommandLine(commandline);
+                                       ExitProcess (ProcessInput (TRUE));
+                               }
+                               else
+                               {
+                                       ExitProcess (0);
+                               }
+                       }
+                       else if (!_tcsicmp (argv[i], _T("/k")))
+                       {
+                               /* This just runs a program and remains */
+                               ++i;
+                               if (argv[i])
+                               {
+                                       _tcscpy (commandline, argv[i]);
+                                       while (argv[++i])
+                                       {
+                                               _tcscat (commandline, " ");
+                                               _tcscat (commandline, argv[i]);
+                                       }
+
+                                       ParseCommandLine(commandline);
+                               }
+                       }
+#ifdef INCLUDE_CMD_COLOR
+                       else if (!_tcsnicmp (argv[i], _T("/t:"), 3))
+                       {
+                               /* process /t (color) argument */
+                               wDefColor = (WORD)strtoul (&argv[i][3], NULL, 16);
+                               wColor = wDefColor;
+                               SetScreenColor (wColor, TRUE);
+                       }
+#endif
+               }
+       }
+
+       /* run cmdstart.bat */
+       if (IsValidFileName (_T("cmdstart.bat")))
+       {
+               ParseCommandLine (_T("cmdstart.bat"));
+       }
+       else if (IsValidFileName (_T("\\cmdstart.bat")))
+       {
+               ParseCommandLine (_T("\\cmdstart.bat"));
+       }
+#ifndef __REACTOS__
+       else
+       {
+               /* try to run cmdstart.bat from install dir */
+               LPTSTR p;
+
+               _tcscpy (commandline, argv[0]);
+               p = _tcsrchr (commandline, _T('\\')) + 1;
+               _tcscpy (p, _T("cmdstart.bat"));
+
+               if (IsValidFileName (_T("commandline")))
+               {
+                       ConErrPrintf ("Running %s...\n", commandline);
+                       ParseCommandLine (commandline);
+               }
+       }
+#endif
+
+#ifdef FEATURE_DIR_STACK
+       /* initialize directory stack */
+       InitDirectoryStack ();
+#endif
+
+
+#ifdef FEATURE_HISTORY
+       /*initialize history*/
+       InitHistory();
+#endif
+
+       /* Set COMSPEC environment variable */
+       if (0 != GetModuleFileName (NULL, ModuleName, _MAX_PATH + 1))
+       {
+               ModuleName[_MAX_PATH] = _T('\0');
+               SetEnvironmentVariable (_T("COMSPEC"), ModuleName);
+       }
+
+       /* add ctrl break handler */
+       AddBreakHandler ();
+}
+
+
+static VOID Cleanup (int argc, char *argv[])
+{
+       /* run cmdexit.bat */
+       if (IsValidFileName (_T("cmdexit.bat")))
+       {
+               ConErrPrintf ("Running cmdexit.bat...\n");
+               ParseCommandLine (_T("cmdexit.bat"));
+       }
+       else if (IsValidFileName (_T("\\cmdexit.bat")))
+       {
+               ConErrPrintf ("Running \\cmdexit.bat...\n");
+               ParseCommandLine (_T("\\cmdexit.bat"));
+       }
+#ifndef __REACTOS__
+       else
+       {
+               /* try to run cmdexit.bat from install dir */
+               TCHAR commandline[CMDLINE_LENGTH];
+               LPTSTR p;
+
+               _tcscpy (commandline, argv[0]);
+               p = _tcsrchr (commandline, _T('\\')) + 1;
+               _tcscpy (p, _T("cmdexit.bat"));
+
+               if (IsValidFileName (_T("commandline")))
+               {
+                       ConErrPrintf ("Running %s...\n", commandline);
+                       ParseCommandLine (commandline);
+               }
+       }
+#endif
+
+#ifdef FEATURE_ALIASES
+       DestroyAlias ();
+#endif
+
+#ifdef FEATURE_DIECTORY_STACK
+       /* destroy directory stack */
+       DestroyDirectoryStack ();
+#endif
+
+#ifdef INCLUDE_CMD_CHDIR
+       FreeLastPath ();
+#endif
+
+#ifdef FEATURE_HISTORY 
+       CleanHistory();
+#endif
+
+
+       /* remove ctrl break handler */
+       RemoveBreakHandler ();
+       SetConsoleMode( GetStdHandle( STD_INPUT_HANDLE ),
+                       ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT );
+}
+
+
+/*
+ * main function
+ */
+int main (int argc, char *argv[])
+{
+  CONSOLE_SCREEN_BUFFER_INFO Info;
+  INT nExitCode;
+
+  SetFileApisToOEM();
+
+  AllocConsole();
+  if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &Info) == FALSE)
+    {
+      fprintf(stderr, "GetConsoleScreenBufferInfo: Error: %ld\n", GetLastError());
+#ifndef __REACTOS__
+      /* On ReactOS GetConsoleScreenBufferInfo returns an error if the stdin 
+         handle is redirected to a pipe or file. This stops windres from working. */
+      return(1);
+#endif
+    }
+  wColor = Info.wAttributes;
+  wDefColor = wColor;
+
+  /* check switches on command-line */
+  Initialize(argc, argv);
+
+  /* call prompt routine */
+  nExitCode = ProcessInput(FALSE);
+
+  /* do the cleanup */
+  Cleanup(argc, argv);
+
+  return(nExitCode);
+}
+
+/* EOF */