ReactOS Fundraising Campaign 2012
 
€ 4,410 / € 30,000

Information | Donate

Home | Info | Community | Development | myReactOS | Contact Us

  1. Home
  2. Community
  3. Development
  4. myReactOS
  5. Fundraiser 2012

  1. Main Page
  2. Alphabetical List
  3. Data Structures
  4. Directories
  5. File List
  6. Data Fields
  7. Globals
  8. Related Pages

ReactOS Development > Doxygen

cmd.c
Go to the documentation of this file.
00001 /*
00002  *  CMD.C - command-line interface.
00003  *
00004  *
00005  *  History:
00006  *
00007  *    17 Jun 1994 (Tim Norman)
00008  *        started.
00009  *
00010  *    08 Aug 1995 (Matt Rains)
00011  *        I have cleaned up the source code. changes now bring this source
00012  *        into guidelines for recommended programming practice.
00013  *
00014  *        A added the the standard FreeDOS GNU licence test to the
00015  *        initialize() function.
00016  *
00017  *        Started to replace puts() with printf(). this will help
00018  *        standardize output. please follow my lead.
00019  *
00020  *        I have added some constants to help making changes easier.
00021  *
00022  *    15 Dec 1995 (Tim Norman)
00023  *        major rewrite of the code to make it more efficient and add
00024  *        redirection support (finally!)
00025  *
00026  *    06 Jan 1996 (Tim Norman)
00027  *        finished adding redirection support!  Changed to use our own
00028  *        exec code (MUCH thanks to Svante Frey!!)
00029  *
00030  *    29 Jan 1996 (Tim Norman)
00031  *        added support for CHDIR, RMDIR, MKDIR, and ERASE, as per
00032  *        suggestion of Steffan Kaiser
00033  *
00034  *        changed "file not found" error message to "bad command or
00035  *        filename" thanks to Dustin Norman for noticing that confusing
00036  *        message!
00037  *
00038  *        changed the format to call internal commands (again) so that if
00039  *        they want to split their commands, they can do it themselves
00040  *        (none of the internal functions so far need that much power, anyway)
00041  *
00042  *    27 Aug 1996 (Tim Norman)
00043  *        added in support for Oliver Mueller's ALIAS command
00044  *
00045  *    14 Jun 1997 (Steffan Kaiser)
00046  *        added ctrl-break handling and error level
00047  *
00048  *    16 Jun 1998 (Rob Lake)
00049  *        Runs command.com if /P is specified in command line.  Command.com
00050  *        also stays permanent.  If /C is in the command line, starts the
00051  *        program next in the line.
00052  *
00053  *    21 Jun 1998 (Rob Lake)
00054  *        Fixed up /C so that arguments for the program
00055  *
00056  *    08-Jul-1998 (John P. Price)
00057  *        Now sets COMSPEC environment variable
00058  *        misc clean up and optimization
00059  *        added date and time commands
00060  *        changed to using spawnl instead of exec.  exec does not copy the
00061  *        environment to the child process!
00062  *
00063  *    14 Jul 1998 (Hans B Pufal)
00064  *        Reorganised source to be more efficient and to more closely
00065  *        follow MS-DOS conventions. (eg %..% environment variable
00066  *        replacement works form command line as well as batch file.
00067  *
00068  *        New organisation also properly support nested batch files.
00069  *
00070  *        New command table structure is half way towards providing a
00071  *        system in which COMMAND will find out what internal commands
00072  *        are loaded
00073  *
00074  *    24 Jul 1998 (Hans B Pufal) [HBP_003]
00075  *        Fixed return value when called with /C option
00076  *
00077  *    27 Jul 1998  John P. Price
00078  *        added config.h include
00079  *
00080  *    28 Jul 1998  John P. Price
00081  *        added showcmds function to show commands and options available
00082  *
00083  *    07-Aug-1998 (John P Price <linux-guru@gcfl.net>)
00084  *        Fixed carrage return output to better match MSDOS with echo
00085  *        on or off. (marked with "JPP 19980708")
00086  *
00087  *    07-Dec-1998 (Eric Kohl)
00088  *        First ReactOS release.
00089  *        Extended length of commandline buffers to 512.
00090  *
00091  *    13-Dec-1998 (Eric Kohl)
00092  *        Added COMSPEC environment variable.
00093  *        Added "/t" support (color) on cmd command line.
00094  *
00095  *    07-Jan-1999 (Eric Kohl)
00096  *        Added help text ("cmd /?").
00097  *
00098  *    25-Jan-1999 (Eric Kohl)
00099  *        Unicode and redirection safe!
00100  *        Fixed redirections and piping.
00101  *        Piping is based on temporary files, but basic support
00102  *        for anonymous pipes already exists.
00103  *
00104  *    27-Jan-1999 (Eric Kohl)
00105  *        Replaced spawnl() by CreateProcess().
00106  *
00107  *    22-Oct-1999 (Eric Kohl)
00108  *        Added break handler.
00109  *
00110  *    15-Dec-1999 (Eric Kohl)
00111  *        Fixed current directory
00112  *
00113  *    28-Dec-1999 (Eric Kohl)
00114  *        Restore window title after program/batch execution
00115  *
00116  *    03-Feb-2001 (Eric Kohl)
00117  *        Workaround because argc[0] is NULL under ReactOS
00118  *
00119  *    23-Feb-2001 (Carl Nettelblad <cnettel@hem.passagen.se>)
00120  *        %envvar% replacement conflicted with for.
00121  *
00122  *    30-Apr-2004 (Filip Navara <xnavara@volny.cz>)
00123  *       Make MakeSureDirectoryPathExistsEx unicode safe.
00124  *
00125  *    28-Mai-2004
00126  *       Removed MakeSureDirectoryPathExistsEx.
00127  *       Use the current directory if GetTempPath fails.
00128  *
00129  *    12-Jul-2004 (Jens Collin <jens.collin@lakhei.com>)
00130  *       Added ShellExecute call when all else fails to be able to "launch" any file.
00131  *
00132  *    02-Apr-2005 (Magnus Olsen <magnus@greatlord.com>)
00133  *        Remove all hardcode string to En.rc
00134  *
00135  *    06-May-2005 (Klemens Friedl <frik85@gmail.com>)
00136  *        Add 'help' command (list all commands plus description)
00137  *
00138  *    06-jul-2005 (Magnus Olsen <magnus@greatlord.com>)
00139  *        translate '%errorlevel%' to the internal value.
00140  *        Add proper memmory alloc ProcessInput, the error
00141  *        handling for memmory handling need to be improve
00142  */
00143 
00144 #include <precomp.h>
00145 #include <reactos/buildno.h>
00146 #include <reactos/version.h>
00147 
00148 #ifndef NT_SUCCESS
00149 #define NT_SUCCESS(StatCode)  ((NTSTATUS)(StatCode) >= 0)
00150 #endif
00151 
00152 typedef NTSTATUS (WINAPI *NtQueryInformationProcessProc)(HANDLE, PROCESSINFOCLASS,
00153                                                           PVOID, ULONG, PULONG);
00154 typedef NTSTATUS (WINAPI *NtReadVirtualMemoryProc)(HANDLE, PVOID, PVOID, ULONG, PULONG);
00155 
00156 BOOL bExit = FALSE;       /* indicates EXIT was typed */
00157 BOOL bCanExit = TRUE;     /* indicates if this shell is exitable */
00158 BOOL bCtrlBreak = FALSE;  /* Ctrl-Break or Ctrl-C hit */
00159 BOOL bIgnoreEcho = FALSE; /* Set this to TRUE to prevent a newline, when executing a command */
00160 INT  nErrorLevel = 0;     /* Errorlevel of last launched external program */
00161 CRITICAL_SECTION ChildProcessRunningLock;
00162 BOOL bUnicodeOutput = FALSE;
00163 BOOL bDisableBatchEcho = FALSE;
00164 BOOL bDelayedExpansion = FALSE;
00165 DWORD dwChildProcessId = 0;
00166 OSVERSIONINFO osvi;
00167 HANDLE hIn;
00168 HANDLE hOut;
00169 LPTSTR lpOriginalEnvironment;
00170 HANDLE CMD_ModuleHandle;
00171 
00172 static NtQueryInformationProcessProc NtQueryInformationProcessPtr = NULL;
00173 static NtReadVirtualMemoryProc       NtReadVirtualMemoryPtr = NULL;
00174 
00175 #ifdef INCLUDE_CMD_COLOR
00176 WORD wDefColor;           /* default color */
00177 #endif
00178 
00179 /*
00180  * convert
00181  *
00182  * insert commas into a number
00183  */
00184 INT
00185 ConvertULargeInteger(ULONGLONG num, LPTSTR des, UINT len, BOOL bPutSeperator)
00186 {
00187     TCHAR temp[39];   /* maximum length with nNumberGroups == 1 */
00188     UINT  n, iTarget;
00189 
00190     if (len <= 1)
00191         return 0;
00192 
00193     n = 0;
00194     iTarget = nNumberGroups;
00195     if (!nNumberGroups)
00196         bPutSeperator = FALSE;
00197 
00198     do
00199     {
00200         if (iTarget == n && bPutSeperator)
00201         {
00202             iTarget += nNumberGroups + 1;
00203             temp[38 - n++] = cThousandSeparator;
00204         }
00205         temp[38 - n++] = (TCHAR)(num % 10) + _T('0');
00206         num /= 10;
00207     } while (num > 0);
00208     if (n > len-1)
00209         n = len-1;
00210 
00211     memcpy(des, temp + 39 - n, n * sizeof(TCHAR));
00212     des[n] = _T('\0');
00213 
00214     return n;
00215 }
00216 
00217 /*
00218  * Is a process a console process?
00219  */
00220 static BOOL IsConsoleProcess(HANDLE Process)
00221 {
00222     NTSTATUS Status;
00223     PROCESS_BASIC_INFORMATION Info;
00224     PEB ProcessPeb;
00225     ULONG BytesRead;
00226 
00227     if (NULL == NtQueryInformationProcessPtr || NULL == NtReadVirtualMemoryPtr)
00228     {
00229         return TRUE;
00230     }
00231 
00232     Status = NtQueryInformationProcessPtr (
00233         Process, ProcessBasicInformation,
00234         &Info, sizeof(PROCESS_BASIC_INFORMATION), NULL);
00235     if (! NT_SUCCESS(Status))
00236     {
00237         WARN ("NtQueryInformationProcess failed with status %08x\n", Status);
00238         return TRUE;
00239     }
00240     Status = NtReadVirtualMemoryPtr (
00241         Process, Info.PebBaseAddress, &ProcessPeb,
00242         sizeof(PEB), &BytesRead);
00243     if (! NT_SUCCESS(Status) || sizeof(PEB) != BytesRead)
00244     {
00245         WARN ("Couldn't read virt mem status %08x bytes read %lu\n", Status, BytesRead);
00246         return TRUE;
00247     }
00248 
00249     return IMAGE_SUBSYSTEM_WINDOWS_CUI == ProcessPeb.ImageSubsystem;
00250 }
00251 
00252 
00253 
00254 #ifdef _UNICODE
00255 #define SHELLEXECUTETEXT    "ShellExecuteExW"
00256 #else
00257 #define SHELLEXECUTETEXT    "ShellExecuteExA"
00258 #endif
00259 
00260 typedef BOOL (WINAPI *MYEX)(LPSHELLEXECUTEINFO lpExecInfo);
00261 
00262 HANDLE RunFile(DWORD flags, LPTSTR filename, LPTSTR params,
00263                LPTSTR directory, INT show)
00264 {
00265     SHELLEXECUTEINFO sei;
00266     HMODULE     hShell32;
00267     MYEX        hShExt;
00268     BOOL        ret;
00269 
00270     TRACE ("RunFile(%s)\n", debugstr_aw(filename));
00271     hShell32 = LoadLibrary(_T("SHELL32.DLL"));
00272     if (!hShell32)
00273     {
00274         WARN ("RunFile: couldn't load SHELL32.DLL!\n");
00275         return NULL;
00276     }
00277 
00278     hShExt = (MYEX)(FARPROC)GetProcAddress(hShell32, SHELLEXECUTETEXT);
00279     if (!hShExt)
00280     {
00281         WARN ("RunFile: couldn't find ShellExecuteExA/W in SHELL32.DLL!\n");
00282         FreeLibrary(hShell32);
00283         return NULL;
00284     }
00285 
00286     TRACE ("RunFile: ShellExecuteExA/W is at %x\n", hShExt);
00287 
00288     memset(&sei, 0, sizeof sei);
00289     sei.cbSize = sizeof sei;
00290     sei.fMask = flags;
00291     sei.lpFile = filename;
00292     sei.lpParameters = params;
00293     sei.lpDirectory = directory;
00294     sei.nShow = show;
00295     ret = hShExt(&sei);
00296 
00297     TRACE ("RunFile: ShellExecuteExA/W returned 0x%p\n", ret);
00298 
00299     FreeLibrary(hShell32);
00300     return ret ? sei.hProcess : NULL;
00301 }
00302 
00303 
00304 
00305 /*
00306  * This command (in first) was not found in the command table
00307  *
00308  * Full  - buffer to hold whole command line
00309  * First - first word on command line
00310  * Rest  - rest of command line
00311  */
00312 
00313 static INT
00314 Execute (LPTSTR Full, LPTSTR First, LPTSTR Rest, PARSED_COMMAND *Cmd)
00315 {
00316     TCHAR szFullName[MAX_PATH];
00317     TCHAR *first, *rest, *dot;
00318     TCHAR szWindowTitle[MAX_PATH];
00319     DWORD dwExitCode = 0;
00320     TCHAR *FirstEnd;
00321     TCHAR szFullCmdLine [CMDLINE_LENGTH];
00322 
00323     TRACE ("Execute: \'%s\' \'%s\'\n", debugstr_aw(First), debugstr_aw(Rest));
00324 
00325     /* Though it was already parsed once, we have a different set of rules
00326        for parsing before we pass to CreateProccess */
00327     if (First[0] == _T('/') || (First[0] && First[1] == _T(':')))
00328     {
00329         /* Use the entire first word as the program name (no change) */
00330         FirstEnd = First + _tcslen(First);
00331     }
00332     else
00333     {
00334         /* If present in the first word, spaces and ,;=/ end the program
00335          * name and become the beginning of its parameters. */
00336         BOOL bInside = FALSE;
00337         for (FirstEnd = First; *FirstEnd; FirstEnd++)
00338         {
00339             if (!bInside && (_istspace(*FirstEnd) || _tcschr(_T(",;=/"), *FirstEnd)))
00340                 break;
00341             bInside ^= *FirstEnd == _T('"');
00342         }
00343     }
00344 
00345     /* Copy the new first/rest into the buffer */
00346     first = Full;
00347     rest = &Full[FirstEnd - First + 1];
00348     _tcscpy(rest, FirstEnd);
00349     _tcscat(rest, Rest);
00350     *FirstEnd = _T('\0');
00351     _tcscpy(first, First);
00352 
00353     /* check for a drive change */
00354     if ((_istalpha (first[0])) && (!_tcscmp (first + 1, _T(":"))))
00355     {
00356         BOOL working = TRUE;
00357         if (!SetCurrentDirectory(first))
00358         /* Guess they changed disc or something, handle that gracefully and get to root */
00359         {
00360             TCHAR str[4];
00361             str[0]=first[0];
00362             str[1]=_T(':');
00363             str[2]=_T('\\');
00364             str[3]=0;
00365             working = SetCurrentDirectory(str);
00366         }
00367 
00368         if (!working) ConErrResPuts (STRING_FREE_ERROR1);
00369         return !working;
00370     }
00371 
00372     /* get the PATH environment variable and parse it */
00373     /* search the PATH environment variable for the binary */
00374     StripQuotes(First);
00375     if (!SearchForExecutable(First, szFullName))
00376     {
00377         error_bad_command(first);
00378         return 1;
00379     }
00380 
00381     GetConsoleTitle (szWindowTitle, MAX_PATH);
00382 
00383     /* check if this is a .BAT or .CMD file */
00384     dot = _tcsrchr (szFullName, _T('.'));
00385     if (dot && (!_tcsicmp (dot, _T(".bat")) || !_tcsicmp (dot, _T(".cmd"))))
00386     {
00387         while (*rest == _T(' '))
00388             rest++;
00389         TRACE ("[BATCH: %s %s]\n", debugstr_aw(szFullName), debugstr_aw(rest));
00390         dwExitCode = Batch(szFullName, first, rest, Cmd);
00391     }
00392     else
00393     {
00394         /* exec the program */
00395         PROCESS_INFORMATION prci;
00396         STARTUPINFO stui;
00397 
00398         /* build command line for CreateProcess(): FullName + " " + rest */
00399         _tcscpy(szFullCmdLine, szFullName);
00400 
00401         if (*rest)
00402         {
00403             _tcsncat(szFullCmdLine, _T(" "), CMDLINE_LENGTH - _tcslen(szFullCmdLine));
00404             _tcsncat(szFullCmdLine, rest, CMDLINE_LENGTH - _tcslen(szFullCmdLine));
00405         }
00406 
00407         TRACE ("[EXEC: %s]\n", debugstr_aw(szFullCmdLine));
00408 
00409         /* fill startup info */
00410         memset (&stui, 0, sizeof (STARTUPINFO));
00411         stui.cb = sizeof (STARTUPINFO);
00412         stui.dwFlags = STARTF_USESHOWWINDOW;
00413         stui.wShowWindow = SW_SHOWDEFAULT;
00414 
00415         // return console to standard mode
00416         SetConsoleMode (GetStdHandle(STD_INPUT_HANDLE),
00417                         ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT );
00418 
00419         if (CreateProcess (szFullName,
00420                            szFullCmdLine,
00421                            NULL,
00422                            NULL,
00423                            TRUE,
00424                            0,           /* CREATE_NEW_PROCESS_GROUP */
00425                            NULL,
00426                            NULL,
00427                            &stui,
00428                            &prci))
00429 
00430         {
00431             CloseHandle(prci.hThread);
00432         }
00433         else
00434         {
00435             // See if we can run this with ShellExecute() ie myfile.xls
00436             prci.hProcess = RunFile(SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE,
00437                                     szFullName,
00438                                     rest,
00439                                     NULL,
00440                                     SW_SHOWNORMAL);
00441         }
00442 
00443         if (prci.hProcess != NULL)
00444         {
00445             if (IsConsoleProcess(prci.hProcess))
00446             {
00447                 EnterCriticalSection(&ChildProcessRunningLock);
00448                 dwChildProcessId = prci.dwProcessId;
00449 
00450                 WaitForSingleObject (prci.hProcess, INFINITE);
00451 
00452                 LeaveCriticalSection(&ChildProcessRunningLock);
00453 
00454                 GetExitCodeProcess (prci.hProcess, &dwExitCode);
00455                 nErrorLevel = (INT)dwExitCode;
00456             }
00457             CloseHandle (prci.hProcess);
00458         }
00459         else
00460         {
00461             TRACE ("[ShellExecute failed!: %s]\n", debugstr_aw(Full));
00462             error_bad_command (first);
00463             dwExitCode = 1;
00464         }
00465 
00466         // restore console mode
00467         SetConsoleMode (
00468             GetStdHandle( STD_INPUT_HANDLE ),
00469             ENABLE_PROCESSED_INPUT );
00470     }
00471 
00472     /* Get code page if it has been change */
00473     InputCodePage= GetConsoleCP();
00474     OutputCodePage = GetConsoleOutputCP();
00475     SetConsoleTitle (szWindowTitle);
00476 
00477     return dwExitCode;
00478 }
00479 
00480 
00481 /*
00482  * look through the internal commands and determine whether or not this
00483  * command is one of them.  If it is, call the command.  If not, call
00484  * execute to run it as an external program.
00485  *
00486  * first - first word on command line
00487  * rest  - rest of command line
00488  */
00489 
00490 INT
00491 DoCommand(LPTSTR first, LPTSTR rest, PARSED_COMMAND *Cmd)
00492 {
00493     TCHAR *com;
00494     TCHAR *cp;
00495     LPTSTR param;   /* pointer to command's parameters */
00496     INT cl;
00497     LPCOMMAND cmdptr;
00498     BOOL nointernal = FALSE;
00499     INT ret;
00500 
00501     TRACE ("DoCommand: (\'%s\' \'%s\')\n", debugstr_aw(first), debugstr_aw(rest));
00502 
00503     /* full command line */
00504     com = cmd_alloc((_tcslen(first) + _tcslen(rest) + 2) * sizeof(TCHAR));
00505     if (com == NULL)
00506     {
00507         error_out_of_memory();
00508         return 1;
00509     }
00510 
00511     /* If present in the first word, these characters end the name of an
00512      * internal command and become the beginning of its parameters. */
00513     cp = first + _tcscspn(first, _T("\t +,/;=[]"));
00514 
00515     for (cl = 0; cl < (cp - first); cl++)
00516     {
00517         /* These characters do it too, but if one of them is present,
00518          * then we check to see if the word is a file name and skip
00519          * checking for internal commands if so.
00520          * This allows running programs with names like "echo.exe" */
00521         if (_tcschr(_T(".:\\"), first[cl]))
00522         {
00523             TCHAR tmp = *cp;
00524             *cp = _T('\0');
00525             nointernal = IsExistingFile(first);
00526             *cp = tmp;
00527             break;
00528         }
00529     }
00530 
00531     /* Scan internal command table */
00532     for (cmdptr = cmds; !nointernal && cmdptr->name; cmdptr++)
00533     {
00534         if (!_tcsnicmp(first, cmdptr->name, cl) && cmdptr->name[cl] == _T('\0'))
00535         {
00536             _tcscpy(com, first);
00537             _tcscat(com, rest);
00538             param = &com[cl];
00539 
00540             /* Skip over whitespace to rest of line, exclude 'echo' command */
00541             if (_tcsicmp(cmdptr->name, _T("echo")) != 0)
00542                 while (_istspace(*param))
00543                     param++;
00544             ret = cmdptr->func(param);
00545             cmd_free(com);
00546             return ret;
00547         }
00548     }
00549 
00550     ret = Execute(com, first, rest, Cmd);
00551     cmd_free(com);
00552     return ret;
00553 }
00554 
00555 
00556 /*
00557  * process the command line and execute the appropriate functions
00558  * full input/output redirection and piping are supported
00559  */
00560 
00561 INT ParseCommandLine (LPTSTR cmd)
00562 {
00563     INT Ret = 0;
00564     PARSED_COMMAND *Cmd = ParseCommand(cmd);
00565     if (Cmd)
00566     {
00567         Ret = ExecuteCommand(Cmd);
00568         FreeCommand(Cmd);
00569     }
00570     return Ret;
00571 }
00572 
00573 /* Execute a command without waiting for it to finish. If it's an internal
00574  * command or batch file, we must create a new cmd.exe process to handle it.
00575  * TODO: For now, this just always creates a cmd.exe process.
00576  *       This works, but is inefficient for running external programs,
00577  *       which could just be run directly. */
00578 static HANDLE
00579 ExecuteAsync(PARSED_COMMAND *Cmd)
00580 {
00581     TCHAR CmdPath[MAX_PATH];
00582     TCHAR CmdParams[CMDLINE_LENGTH], *ParamsEnd;
00583     STARTUPINFO stui;
00584     PROCESS_INFORMATION prci;
00585 
00586     /* Get the path to cmd.exe */
00587     GetModuleFileName(NULL, CmdPath, MAX_PATH);
00588 
00589     /* Build the parameter string to pass to cmd.exe */
00590     ParamsEnd = _stpcpy(CmdParams, _T("/S/D/C\""));
00591     ParamsEnd = Unparse(Cmd, ParamsEnd, &CmdParams[CMDLINE_LENGTH - 2]);
00592     if (!ParamsEnd)
00593     {
00594         error_out_of_memory();
00595         return NULL;
00596     }
00597     _tcscpy(ParamsEnd, _T("\""));
00598 
00599     memset(&stui, 0, sizeof stui);
00600     stui.cb = sizeof(STARTUPINFO);
00601     if (!CreateProcess(CmdPath, CmdParams, NULL, NULL, TRUE, 0,
00602                        NULL, NULL, &stui, &prci))
00603     {
00604         ErrorMessage(GetLastError(), NULL);
00605         return NULL;
00606     }
00607 
00608     CloseHandle(prci.hThread);
00609     return prci.hProcess;
00610 }
00611 
00612 static VOID
00613 ExecutePipeline(PARSED_COMMAND *Cmd)
00614 {
00615 #ifdef FEATURE_REDIRECTION
00616     HANDLE hInput = NULL;
00617     HANDLE hOldConIn = GetStdHandle(STD_INPUT_HANDLE);
00618     HANDLE hOldConOut = GetStdHandle(STD_OUTPUT_HANDLE);
00619     HANDLE hProcess[MAXIMUM_WAIT_OBJECTS];
00620     INT nProcesses = 0;
00621     DWORD dwExitCode;
00622 
00623     /* Do all but the last pipe command */
00624     do
00625     {
00626         HANDLE hPipeRead, hPipeWrite;
00627         if (nProcesses > (MAXIMUM_WAIT_OBJECTS - 2))
00628         {
00629             error_too_many_parameters(_T("|"));
00630             goto failed;
00631         }
00632 
00633         /* Create the pipe that this process will write into.
00634          * Make the handles non-inheritable initially, because this
00635          * process shouldn't inherit the reading handle. */
00636         if (!CreatePipe(&hPipeRead, &hPipeWrite, NULL, 0))
00637         {
00638             error_no_pipe();
00639             goto failed;
00640         }
00641 
00642         /* The writing side of the pipe is STDOUT for this process */
00643         SetHandleInformation(hPipeWrite, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
00644         SetStdHandle(STD_OUTPUT_HANDLE, hPipeWrite);
00645 
00646         /* Execute it (error check is done later for easier cleanup) */
00647         hProcess[nProcesses] = ExecuteAsync(Cmd->Subcommands);
00648         CloseHandle(hPipeWrite);
00649         if (hInput)
00650             CloseHandle(hInput);
00651 
00652         /* The reading side of the pipe will be STDIN for the next process */
00653         SetHandleInformation(hPipeRead, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
00654         SetStdHandle(STD_INPUT_HANDLE, hPipeRead);
00655         hInput = hPipeRead;
00656 
00657         if (!hProcess[nProcesses])
00658             goto failed;
00659         nProcesses++;
00660 
00661         Cmd = Cmd->Subcommands->Next;
00662     } while (Cmd->Type == C_PIPE);
00663 
00664     /* The last process uses the original STDOUT */
00665     SetStdHandle(STD_OUTPUT_HANDLE, hOldConOut);
00666     hProcess[nProcesses] = ExecuteAsync(Cmd);
00667     if (!hProcess[nProcesses])
00668         goto failed;
00669     nProcesses++;
00670     CloseHandle(hInput);
00671     SetStdHandle(STD_INPUT_HANDLE, hOldConIn);
00672 
00673     /* Wait for all processes to complete */
00674     EnterCriticalSection(&ChildProcessRunningLock);
00675     WaitForMultipleObjects(nProcesses, hProcess, TRUE, INFINITE);
00676     LeaveCriticalSection(&ChildProcessRunningLock);
00677 
00678     /* Use the exit code of the last process in the pipeline */
00679     GetExitCodeProcess(hProcess[nProcesses - 1], &dwExitCode);
00680     nErrorLevel = (INT)dwExitCode;
00681 
00682     while (--nProcesses >= 0)
00683         CloseHandle(hProcess[nProcesses]);
00684     return;
00685 
00686 failed:
00687     if (hInput)
00688         CloseHandle(hInput);
00689     while (--nProcesses >= 0)
00690     {
00691         TerminateProcess(hProcess[nProcesses], 0);
00692         CloseHandle(hProcess[nProcesses]);
00693     }
00694     SetStdHandle(STD_INPUT_HANDLE, hOldConIn);
00695     SetStdHandle(STD_OUTPUT_HANDLE, hOldConOut);
00696 #endif
00697 }
00698 
00699 INT
00700 ExecuteCommand(PARSED_COMMAND *Cmd)
00701 {
00702     PARSED_COMMAND *Sub;
00703     LPTSTR First, Rest;
00704     INT Ret = 0;
00705 
00706     if (!PerformRedirection(Cmd->Redirections))
00707         return 1;
00708 
00709     switch (Cmd->Type)
00710     {
00711     case C_COMMAND:
00712         Ret = 1;
00713         First = DoDelayedExpansion(Cmd->Command.First);
00714         if (First)
00715         {
00716             Rest = DoDelayedExpansion(Cmd->Command.Rest);
00717             if (Rest)
00718             {
00719                 Ret = DoCommand(First, Rest, Cmd);
00720                 cmd_free(Rest);
00721             }
00722             cmd_free(First);
00723         }
00724         break;
00725     case C_QUIET:
00726     case C_BLOCK:
00727     case C_MULTI:
00728         for (Sub = Cmd->Subcommands; Sub; Sub = Sub->Next)
00729             Ret = ExecuteCommand(Sub);
00730         break;
00731     case C_IFFAILURE:
00732         Sub = Cmd->Subcommands;
00733         Ret = ExecuteCommand(Sub);
00734         if (Ret != 0)
00735         {
00736             nErrorLevel = Ret;
00737             Ret = ExecuteCommand(Sub->Next);
00738         }
00739         break;
00740     case C_IFSUCCESS:
00741         Sub = Cmd->Subcommands;
00742         Ret = ExecuteCommand(Sub);
00743         if (Ret == 0)
00744             Ret = ExecuteCommand(Sub->Next);
00745         break;
00746     case C_PIPE:
00747         ExecutePipeline(Cmd);
00748         break;
00749     case C_IF:
00750         Ret = ExecuteIf(Cmd);
00751         break;
00752     case C_FOR:
00753         Ret = ExecuteFor(Cmd);
00754         break;
00755     }
00756 
00757     UndoRedirection(Cmd->Redirections, NULL);
00758     return Ret;
00759 }
00760 
00761 LPTSTR
00762 GetEnvVar(LPCTSTR varName)
00763 {
00764     static LPTSTR ret = NULL;
00765     UINT size;
00766 
00767     cmd_free(ret);
00768     ret = NULL;
00769     size = GetEnvironmentVariable(varName, NULL, 0);
00770     if (size > 0)
00771     {
00772         ret = cmd_alloc(size * sizeof(TCHAR));
00773         if (ret != NULL)
00774             GetEnvironmentVariable(varName, ret, size + 1);
00775     }
00776     return ret;
00777 }
00778 
00779 LPCTSTR
00780 GetEnvVarOrSpecial(LPCTSTR varName)
00781 {
00782     static TCHAR ret[MAX_PATH];
00783 
00784     LPTSTR var = GetEnvVar(varName);
00785     if (var)
00786         return var;
00787 
00788     /* env var doesn't exist, look for a "special" one */
00789     /* %CD% */
00790     if (_tcsicmp(varName,_T("cd")) ==0)
00791     {
00792         GetCurrentDirectory(MAX_PATH, ret);
00793         return ret;
00794     }
00795     /* %TIME% */
00796     else if (_tcsicmp(varName,_T("time")) ==0)
00797     {
00798         return GetTimeString();
00799     }
00800     /* %DATE% */
00801     else if (_tcsicmp(varName,_T("date")) ==0)
00802     {
00803         return GetDateString();
00804     }
00805 
00806     /* %RANDOM% */
00807     else if (_tcsicmp(varName,_T("random")) ==0)
00808     {
00809         /* Get random number */
00810         _itot(rand(),ret,10);
00811         return ret;
00812     }
00813 
00814     /* %CMDCMDLINE% */
00815     else if (_tcsicmp(varName,_T("cmdcmdline")) ==0)
00816     {
00817         return GetCommandLine();
00818     }
00819 
00820     /* %CMDEXTVERSION% */
00821     else if (_tcsicmp(varName,_T("cmdextversion")) ==0)
00822     {
00823         /* Set version number to 2 */
00824         _itot(2,ret,10);
00825         return ret;
00826     }
00827 
00828     /* %ERRORLEVEL% */
00829     else if (_tcsicmp(varName,_T("errorlevel")) ==0)
00830     {
00831         _itot(nErrorLevel,ret,10);
00832         return ret;
00833     }
00834 
00835     return NULL;
00836 }
00837 
00838 /* Handle the %~var syntax */
00839 static LPTSTR
00840 GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, BOOL *))
00841 {
00842     static const TCHAR ModifierTable[] = _T("dpnxfsatz");
00843     enum {
00844         M_DRIVE = 1,   /* D: drive letter */
00845         M_PATH  = 2,   /* P: path */
00846         M_NAME  = 4,   /* N: filename */
00847         M_EXT   = 8,   /* X: extension */
00848         M_FULL  = 16,  /* F: full path (drive+path+name+ext) */
00849         M_SHORT = 32,  /* S: full path (drive+path+name+ext), use short names */
00850         M_ATTR  = 64,  /* A: attributes */
00851         M_TIME  = 128, /* T: modification time */
00852         M_SIZE  = 256, /* Z: file size */
00853     } Modifiers = 0;
00854 
00855     TCHAR *Format, *FormatEnd;
00856     TCHAR *PathVarName = NULL;
00857     LPTSTR Variable;
00858     TCHAR *VarEnd;
00859     BOOL VariableIsParam0;
00860     TCHAR FullPath[MAX_PATH];
00861     TCHAR FixedPath[MAX_PATH], *Filename, *Extension;
00862     HANDLE hFind;
00863     WIN32_FIND_DATA w32fd;
00864     TCHAR *In, *Out;
00865 
00866     static TCHAR Result[CMDLINE_LENGTH];
00867 
00868     /* There is ambiguity between modifier characters and FOR variables;
00869      * the rule that cmd uses is to pick the longest possible match.
00870      * For example, if there is a %n variable, then out of %~anxnd,
00871      * %~anxn will be substituted rather than just %~an. */
00872 
00873     /* First, go through as many modifier characters as possible */
00874     FormatEnd = Format = *pFormat;
00875     while (*FormatEnd && _tcschr(ModifierTable, _totlower(*FormatEnd)))
00876         FormatEnd++;
00877 
00878     if (*FormatEnd == _T('$'))
00879     {
00880         /* $PATH: syntax */
00881         PathVarName = FormatEnd + 1;
00882         FormatEnd = _tcschr(PathVarName, _T(':'));
00883         if (!FormatEnd)
00884             return NULL;
00885 
00886         /* Must be immediately followed by the variable */
00887         Variable = GetVar(*++FormatEnd, &VariableIsParam0);
00888         if (!Variable)
00889             return NULL;
00890     }
00891     else
00892     {
00893         /* Backtrack if necessary to get a variable name match */
00894         while (!(Variable = GetVar(*FormatEnd, &VariableIsParam0)))
00895         {
00896             if (FormatEnd == Format)
00897                 return NULL;
00898             FormatEnd--;
00899         }
00900     }
00901 
00902     for (; Format < FormatEnd && *Format != _T('$'); Format++)
00903         Modifiers |= 1 << (_tcschr(ModifierTable, _totlower(*Format)) - ModifierTable);
00904 
00905     *pFormat = FormatEnd + 1;
00906 
00907     /* Exclude the leading and trailing quotes */
00908     VarEnd = &Variable[_tcslen(Variable)];
00909     if (*Variable == _T('"'))
00910     {
00911         Variable++;
00912         if (VarEnd > Variable && VarEnd[-1] == _T('"'))
00913             VarEnd--;
00914     }
00915 
00916     if ((char *)VarEnd - (char *)Variable >= sizeof Result)
00917         return _T("");
00918     memcpy(Result, Variable, (char *)VarEnd - (char *)Variable);
00919     Result[VarEnd - Variable] = _T('\0');
00920 
00921     if (PathVarName)
00922     {
00923         /* $PATH: syntax - search the directories listed in the
00924          * specified environment variable for the file */
00925         LPTSTR PathVar;
00926         FormatEnd[-1] = _T('\0');
00927         PathVar = GetEnvVar(PathVarName);
00928         FormatEnd[-1] = _T(':');
00929         if (!PathVar ||
00930             !SearchPath(PathVar, Result, NULL, MAX_PATH, FullPath, NULL))
00931         {
00932             return _T("");
00933         }
00934     }
00935     else if (Modifiers == 0)
00936     {
00937         /* For plain %~var with no modifiers, just return the variable without quotes */
00938         return Result;
00939     }
00940     else if (VariableIsParam0)
00941     {
00942         /* Special case: If the variable is %0 and modifier characters are present,
00943          * use the batch file's path (which includes the .bat/.cmd extension)
00944          * rather than the actual %0 variable (which might not). */
00945         _tcscpy(FullPath, bc->BatchFilePath);
00946     }
00947     else
00948     {
00949         /* Convert the variable, now without quotes, to a full path */
00950         if (!GetFullPathName(Result, MAX_PATH, FullPath, NULL))
00951             return _T("");
00952     }
00953 
00954     /* Next step is to change the path to fix letter case (e.g.
00955      * C:\ReAcToS -> C:\ReactOS) and, if requested with the S modifier,
00956      * replace long filenames with short. */
00957 
00958     In = FullPath;
00959     Out = FixedPath;
00960 
00961     /* Copy drive letter */
00962     *Out++ = *In++;
00963     *Out++ = *In++;
00964     *Out++ = *In++;
00965     /* Loop over each \-separated component in the path */
00966     do {
00967         TCHAR *Next = _tcschr(In, _T('\\'));
00968         if (Next)
00969             *Next++ = _T('\0');
00970         /* Use FindFirstFile to get the correct name */
00971         if (Out + _tcslen(In) + 1 >= &FixedPath[MAX_PATH])
00972             return _T("");
00973         _tcscpy(Out, In);
00974         hFind = FindFirstFile(FixedPath, &w32fd);
00975         /* If it doesn't exist, just leave the name as it was given */
00976         if (hFind != INVALID_HANDLE_VALUE)
00977         {
00978             LPTSTR FixedComponent = w32fd.cFileName;
00979             if (*w32fd.cAlternateFileName &&
00980                 ((Modifiers & M_SHORT) || !_tcsicmp(In, w32fd.cAlternateFileName)))
00981             {
00982                 FixedComponent = w32fd.cAlternateFileName;
00983             }
00984             FindClose(hFind);
00985 
00986             if (Out + _tcslen(FixedComponent) + 1 >= &FixedPath[MAX_PATH])
00987                 return _T("");
00988             _tcscpy(Out, FixedComponent);
00989         }
00990         Filename = Out;
00991         Out += _tcslen(Out);
00992         *Out++ = _T('\\');
00993 
00994         In = Next;
00995     } while (In != NULL);
00996     Out[-1] = _T('\0');
00997 
00998     /* Build the result string. Start with attributes, modification time, and
00999      * file size. If the file didn't exist, these fields will all be empty. */
01000     Out = Result;
01001     if (hFind != INVALID_HANDLE_VALUE)
01002     {
01003         if (Modifiers & M_ATTR)
01004         {
01005             static const struct {
01006                 TCHAR Character;
01007                 WORD  Value;
01008             } *Attrib, Table[] = {
01009                 { _T('d'), FILE_ATTRIBUTE_DIRECTORY },
01010                 { _T('r'), FILE_ATTRIBUTE_READONLY },
01011                 { _T('a'), FILE_ATTRIBUTE_ARCHIVE },
01012                 { _T('h'), FILE_ATTRIBUTE_HIDDEN },
01013                 { _T('s'), FILE_ATTRIBUTE_SYSTEM },
01014                 { _T('c'), FILE_ATTRIBUTE_COMPRESSED },
01015                 { _T('o'), FILE_ATTRIBUTE_OFFLINE },
01016                 { _T('t'), FILE_ATTRIBUTE_TEMPORARY },
01017                 { _T('l'), FILE_ATTRIBUTE_REPARSE_POINT },
01018             };
01019             for (Attrib = Table; Attrib != &Table[9]; Attrib++)
01020             {
01021                 *Out++ = w32fd.dwFileAttributes & Attrib->Value
01022                          ? Attrib->Character
01023                          : _T('-');
01024             }
01025             *Out++ = _T(' ');
01026         }
01027         if (Modifiers & M_TIME)
01028         {
01029             FILETIME ft;
01030             SYSTEMTIME st;
01031             FileTimeToLocalFileTime(&w32fd.ftLastWriteTime, &ft);
01032             FileTimeToSystemTime(&ft, &st);
01033 
01034             Out += FormatDate(Out, &st, TRUE);
01035             *Out++ = _T(' ');
01036             Out += FormatTime(Out, &st);
01037             *Out++ = _T(' ');
01038         }
01039         if (Modifiers & M_SIZE)
01040         {
01041             ULARGE_INTEGER Size;
01042             Size.LowPart = w32fd.nFileSizeLow;
01043             Size.HighPart = w32fd.nFileSizeHigh;
01044             Out += _stprintf(Out, _T("%I64u "), Size.QuadPart);
01045         }
01046     }
01047 
01048     /* When using the path-searching syntax or the S modifier,
01049      * at least part of the file path is always included.
01050      * If none of the DPNX modifiers are present, include the full path */
01051     if (PathVarName || (Modifiers & M_SHORT))
01052         if ((Modifiers & (M_DRIVE | M_PATH | M_NAME | M_EXT)) == 0)
01053             Modifiers |= M_FULL;
01054 
01055     /* Now add the requested parts of the name.
01056      * With the F modifier, add all parts to form the full path. */
01057     Extension = _tcsrchr(Filename, _T('.'));
01058     if (Modifiers & (M_DRIVE | M_FULL))
01059     {
01060         *Out++ = FixedPath[0];
01061         *Out++ = FixedPath[1];
01062     }
01063     if (Modifiers & (M_PATH | M_FULL))
01064     {
01065         memcpy(Out, &FixedPath[2], (char *)Filename - (char *)&FixedPath[2]);
01066         Out += Filename - &FixedPath[2];
01067     }
01068     if (Modifiers & (M_NAME | M_FULL))
01069     {
01070         while (*Filename && Filename != Extension)
01071             *Out++ = *Filename++;
01072     }
01073     if (Modifiers & (M_EXT | M_FULL))
01074     {
01075         if (Extension)
01076             Out = _stpcpy(Out, Extension);
01077     }
01078 
01079     /* Trim trailing space which otherwise would appear as a
01080      * result of using the A/T/Z modifiers but no others. */
01081     while (Out != &Result[0] && Out[-1] == _T(' '))
01082         Out--;
01083     *Out = _T('\0');
01084 
01085     return Result;
01086 }
01087 
01088 LPCTSTR
01089 GetBatchVar(TCHAR *varName, UINT *varNameLen)
01090 {
01091     LPCTSTR ret;
01092     TCHAR *varNameEnd;
01093     BOOL dummy;
01094 
01095     *varNameLen = 1;
01096 
01097     switch ( *varName )
01098     {
01099     case _T('~'):
01100         varNameEnd = varName + 1;
01101         ret = GetEnhancedVar(&varNameEnd, FindArg);
01102         if (!ret)
01103         {
01104             error_syntax(varName);
01105             return NULL;
01106         }
01107         *varNameLen = varNameEnd - varName;
01108         return ret;
01109     case _T('0'):
01110     case _T('1'):
01111     case _T('2'):
01112     case _T('3'):
01113     case _T('4'):
01114     case _T('5'):
01115     case _T('6'):
01116     case _T('7'):
01117     case _T('8'):
01118     case _T('9'):
01119         return FindArg(*varName, &dummy);
01120 
01121     case _T('*'):
01122         //
01123         // Copy over the raw params(not including the batch file name
01124         //
01125         return bc->raw_params;
01126 
01127     case _T('%'):
01128         return _T("%");
01129     }
01130     return NULL;
01131 }
01132 
01133 BOOL
01134 SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim)
01135 {
01136 #define APPEND(From, Length) { \
01137     if (Dest + (Length) > DestEnd) \
01138         goto too_long; \
01139     memcpy(Dest, From, (Length) * sizeof(TCHAR)); \
01140     Dest += Length; }
01141 #define APPEND1(Char) { \
01142     if (Dest >= DestEnd) \
01143         goto too_long; \
01144     *Dest++ = Char; }
01145 
01146     TCHAR *DestEnd = Dest + CMDLINE_LENGTH - 1;
01147     const TCHAR *Var;
01148     int VarLength;
01149     TCHAR *SubstStart;
01150     TCHAR EndChr;
01151     while (*Src)
01152     {
01153         if (*Src != Delim)
01154         {
01155             APPEND1(*Src++)
01156             continue;
01157         }
01158 
01159         Src++;
01160         if (bc && Delim == _T('%'))
01161         {
01162             UINT NameLen;
01163             Var = GetBatchVar(Src, &NameLen);
01164             if (Var != NULL)
01165             {
01166                 VarLength = _tcslen(Var);
01167                 APPEND(Var, VarLength)
01168                 Src += NameLen;
01169                 continue;
01170             }
01171         }
01172 
01173         /* Find the end of the variable name. A colon (:) will usually
01174          * end the name and begin the optional modifier, but not if it
01175          * is immediately followed by the delimiter (%VAR:%). */
01176         SubstStart = Src;
01177         while (*Src != Delim && !(*Src == _T(':') && Src[1] != Delim))
01178         {
01179             if (!*Src)
01180                 goto bad_subst;
01181             Src++;
01182         }
01183 
01184         EndChr = *Src;
01185         *Src = _T('\0');
01186         Var = GetEnvVarOrSpecial(SubstStart);
01187         *Src++ = EndChr;
01188         if (Var == NULL)
01189         {
01190             /* In a batch file, %NONEXISTENT% "expands" to an empty string */
01191             if (bc)
01192                 continue;
01193             goto bad_subst;
01194         }
01195         VarLength = _tcslen(Var);
01196 
01197         if (EndChr == Delim)
01198         {
01199             /* %VAR% - use as-is */
01200             APPEND(Var, VarLength)
01201         }
01202         else if (*Src == _T('~'))
01203         {
01204             /* %VAR:~[start][,length]% - substring
01205              * Negative values are offsets from the end */
01206             int Start = _tcstol(Src + 1, &Src, 0);
01207             int End = VarLength;
01208             if (Start < 0)
01209                 Start += VarLength;
01210             Start = max(Start, 0);
01211             Start = min(Start, VarLength);
01212             if (*Src == _T(','))
01213             {
01214                 End = _tcstol(Src + 1, &Src, 0);
01215                 End += (End < 0) ? VarLength : Start;
01216                 End = max(End, Start);
01217                 End = min(End, VarLength);
01218             }
01219             if (*Src++ != Delim)
01220                 goto bad_subst;
01221             APPEND(&Var[Start], End - Start);
01222         }
01223         else
01224         {
01225             /* %VAR:old=new% - replace all occurrences of old with new
01226              * %VAR:*old=new% - replace first occurrence only,
01227              *                  and remove everything before it */
01228             TCHAR *Old, *New;
01229             DWORD OldLength, NewLength;
01230             BOOL Star = FALSE;
01231             int LastMatch = 0, i = 0;
01232 
01233             if (*Src == _T('*'))
01234             {
01235                 Star = TRUE;
01236                 Src++;
01237             }
01238 
01239             /* the string to replace may contain the delimiter */
01240             Src = _tcschr(Old = Src, _T('='));
01241             if (Src == NULL)
01242                 goto bad_subst;
01243             OldLength = Src++ - Old;
01244             if (OldLength == 0)
01245                 goto bad_subst;
01246 
01247             Src = _tcschr(New = Src, Delim);
01248             if (Src == NULL)
01249                 goto bad_subst;
01250             NewLength = Src++ - New;
01251 
01252             while (i < VarLength)
01253             {
01254                 if (_tcsnicmp(&Var[i], Old, OldLength) == 0)
01255                 {
01256                     if (!Star)
01257                         APPEND(&Var[LastMatch], i - LastMatch)
01258                     APPEND(New, NewLength)
01259                     i += OldLength;
01260                     LastMatch = i;
01261                     if (Star)
01262                         break;
01263                     continue;
01264                 }
01265                 i++;
01266             }
01267             APPEND(&Var[LastMatch], VarLength - LastMatch)
01268         }
01269         continue;
01270 
01271     bad_subst:
01272         Src = SubstStart;
01273         if (!bc)
01274             APPEND1(Delim)
01275     }
01276     *Dest = _T('\0');
01277     return TRUE;
01278 too_long:
01279     ConOutResPrintf(STRING_ALIAS_ERROR);
01280     nErrorLevel = 9023;
01281     return FALSE;
01282 #undef APPEND
01283 #undef APPEND1
01284 }
01285 
01286 /* Search the list of FOR contexts for a variable */
01287 static LPTSTR FindForVar(TCHAR Var, BOOL *IsParam0)
01288 {
01289     FOR_CONTEXT *Ctx;
01290     *IsParam0 = FALSE;
01291     for (Ctx = fc; Ctx != NULL; Ctx = Ctx->prev)
01292         if ((UINT)(Var - Ctx->firstvar) < Ctx->varcount)
01293             return Ctx->values[Var - Ctx->firstvar];
01294     return NULL;
01295 }
01296 
01297 BOOL
01298 SubstituteForVars(TCHAR *Src, TCHAR *Dest)
01299 {
01300     TCHAR *DestEnd = &Dest[CMDLINE_LENGTH - 1];
01301     while (*Src)
01302     {
01303         if (Src[0] == _T('%'))
01304         {
01305             BOOL Dummy;
01306             LPTSTR End = &Src[2];
01307             LPTSTR Value = NULL;
01308 
01309             if (Src[1] == _T('~'))
01310                 Value = GetEnhancedVar(&End, FindForVar);
01311 
01312             if (!Value)
01313                 Value = FindForVar(Src[1], &Dummy);
01314 
01315             if (Value)
01316             {
01317                 if (Dest + _tcslen(Value) > DestEnd)
01318                     return FALSE;
01319                 Dest = _stpcpy(Dest, Value);
01320                 Src = End;
01321                 continue;
01322             }
01323         }
01324         /* Not a variable; just copy the character */
01325         if (Dest >= DestEnd)
01326             return FALSE;
01327         *Dest++ = *Src++;
01328     }
01329     *Dest = _T('\0');
01330     return TRUE;
01331 }
01332 
01333 LPTSTR
01334 DoDelayedExpansion(LPTSTR Line)
01335 {
01336     TCHAR Buf1[CMDLINE_LENGTH];
01337     TCHAR Buf2[CMDLINE_LENGTH];
01338 
01339     /* First, substitute FOR variables */
01340     if (!SubstituteForVars(Line, Buf1))
01341         return NULL;
01342 
01343     if (!bDelayedExpansion || !_tcschr(Buf1, _T('!')))
01344         return cmd_dup(Buf1);
01345 
01346     /* FIXME: Delayed substitutions actually aren't quite the same as
01347      * immediate substitutions. In particular, it's possible to escape
01348      * the exclamation point using ^. */
01349     if (!SubstituteVars(Buf1, Buf2, _T('!')))
01350         return NULL;
01351     return cmd_dup(Buf2);
01352 }
01353 
01354 
01355 /*
01356  * do the prompt/input/process loop
01357  *
01358  */
01359 
01360 BOOL
01361 ReadLine (TCHAR *commandline, BOOL bMore)
01362 {
01363     TCHAR readline[CMDLINE_LENGTH];
01364     LPTSTR ip;
01365 
01366     /* if no batch input then... */
01367     if (bc == NULL)
01368     {
01369         if (bMore)
01370         {
01371             ConOutResPrintf(STRING_MORE);
01372         }
01373         else
01374         {
01375             /* JPP 19980807 - if echo off, don't print prompt */
01376             if (bEcho)
01377             {
01378                 if (!bIgnoreEcho)
01379                     ConOutChar('\n');
01380                 PrintPrompt();
01381             }
01382         }
01383 
01384         if (!ReadCommand(readline, CMDLINE_LENGTH - 1))
01385         {
01386             bExit = TRUE;
01387             return FALSE;
01388         }
01389 
01390         if (CheckCtrlBreak(BREAK_INPUT))
01391         {
01392             ConOutPuts(_T("\n"));
01393             return FALSE;
01394         }
01395         ip = readline;
01396     }
01397     else
01398     {
01399         ip = ReadBatchLine();
01400         if (!ip)
01401             return FALSE;
01402     }
01403 
01404     return SubstituteVars(ip, commandline, _T('%'));
01405 }
01406 
01407 static VOID
01408 ProcessInput()
01409 {
01410     PARSED_COMMAND *Cmd;
01411 
01412     while (!bCanExit || !bExit)
01413     {
01414         Cmd = ParseCommand(NULL);
01415         if (!Cmd)
01416             continue;
01417 
01418         ExecuteCommand(Cmd);
01419         FreeCommand(Cmd);
01420     }
01421 }
01422 
01423 
01424 /*
01425  * control-break handler.
01426  */
01427 BOOL WINAPI BreakHandler (DWORD dwCtrlType)
01428 {
01429 
01430     DWORD           dwWritten;
01431     INPUT_RECORD    rec;
01432     static BOOL SelfGenerated = FALSE;
01433 
01434     if ((dwCtrlType != CTRL_C_EVENT) &&
01435         (dwCtrlType != CTRL_BREAK_EVENT))
01436     {
01437         return FALSE;
01438     }
01439     else
01440     {
01441         if(SelfGenerated)
01442         {
01443             SelfGenerated = FALSE;
01444             return TRUE;
01445         }
01446     }
01447 
01448     if (!TryEnterCriticalSection(&ChildProcessRunningLock))
01449     {
01450         SelfGenerated = TRUE;
01451         GenerateConsoleCtrlEvent (dwCtrlType, 0);
01452         return TRUE;
01453     }
01454     else
01455     {
01456         LeaveCriticalSection(&ChildProcessRunningLock);
01457     }
01458 
01459     rec.EventType = KEY_EVENT;
01460     rec.Event.KeyEvent.bKeyDown = TRUE;
01461     rec.Event.KeyEvent.wRepeatCount = 1;
01462     rec.Event.KeyEvent.wVirtualKeyCode = _T('C');
01463     rec.Event.KeyEvent.wVirtualScanCode = _T('C') - 35;
01464     rec.Event.KeyEvent.uChar.AsciiChar = _T('C');
01465     rec.Event.KeyEvent.uChar.UnicodeChar = _T('C');
01466     rec.Event.KeyEvent.dwControlKeyState = RIGHT_CTRL_PRESSED;
01467 
01468     WriteConsoleInput(
01469         hIn,
01470         &rec,
01471         1,
01472         &dwWritten);
01473 
01474     bCtrlBreak = TRUE;
01475     /* FIXME: Handle batch files */
01476 
01477     //ConOutPrintf(_T("^C"));
01478 
01479 
01480     return TRUE;
01481 }
01482 
01483 
01484 VOID AddBreakHandler (VOID)
01485 {
01486     SetConsoleCtrlHandler ((PHANDLER_ROUTINE)BreakHandler, TRUE);
01487 }
01488 
01489 
01490 VOID RemoveBreakHandler (VOID)
01491 {
01492     SetConsoleCtrlHandler ((PHANDLER_ROUTINE)BreakHandler, FALSE);
01493 }
01494 
01495 
01496 /*
01497  * show commands and options that are available.
01498  *
01499  */
01500 #if 0
01501 static VOID
01502 ShowCommands (VOID)
01503 {
01504     /* print command list */
01505     ConOutResPuts(STRING_CMD_HELP1);
01506     PrintCommandList();
01507 
01508     /* print feature list */
01509     ConOutResPuts(STRING_CMD_HELP2);
01510 
01511 #ifdef FEATURE_ALIASES
01512     ConOutResPuts(STRING_CMD_HELP3);
01513 #endif
01514 #ifdef FEATURE_HISTORY
01515     ConOutResPuts(STRING_CMD_HELP4);
01516 #endif
01517 #ifdef FEATURE_UNIX_FILENAME_COMPLETION
01518     ConOutResPuts(STRING_CMD_HELP5);
01519 #endif
01520 #ifdef FEATURE_DIRECTORY_STACK
01521     ConOutResPuts(STRING_CMD_HELP6);
01522 #endif
01523 #ifdef FEATURE_REDIRECTION
01524     ConOutResPuts(STRING_CMD_HELP7);
01525 #endif
01526     ConOutChar(_T('\n'));
01527 }
01528 #endif
01529 
01530 static VOID
01531 ExecuteAutoRunFile(HKEY hkeyRoot)
01532 {
01533     TCHAR autorun[2048];
01534     DWORD len = sizeof autorun;
01535     HKEY hkey;
01536 
01537     if (RegOpenKeyEx(hkeyRoot,
01538                     _T("SOFTWARE\\Microsoft\\Command Processor"),
01539                     0,
01540                     KEY_READ,
01541                     &hkey ) == ERROR_SUCCESS)
01542     {
01543         if(RegQueryValueEx(hkey,
01544                            _T("AutoRun"),
01545                            0,
01546                            0,
01547                            (LPBYTE)autorun,
01548                            &len) == ERROR_SUCCESS)
01549         {
01550             if (*autorun)
01551                 ParseCommandLine(autorun);
01552         }
01553         RegCloseKey(hkey);
01554     }
01555 }
01556 
01557 /* Get the command that comes after a /C or /K switch */
01558 static VOID
01559 GetCmdLineCommand(TCHAR *commandline, TCHAR *ptr, BOOL AlwaysStrip)
01560 {
01561     TCHAR *LastQuote;
01562 
01563     while (_istspace(*ptr))
01564         ptr++;
01565 
01566     /* Remove leading quote, find final quote */
01567     if (*ptr == _T('"') &&
01568         (LastQuote = _tcsrchr(++ptr, _T('"'))) != NULL)
01569     {
01570         TCHAR *Space;
01571         /* Under certain circumstances, all quotes are preserved.
01572          * CMD /? documents these conditions as follows:
01573          *  1. No /S switch
01574          *  2. Exactly two quotes
01575          *  3. No "special characters" between the quotes
01576          *     (CMD /? says &<>()@^| but parentheses did not
01577          *     trigger this rule when I tested them.)
01578          *  4. Whitespace exists between the quotes
01579          *  5. Enclosed string is an executable filename
01580          */
01581         *LastQuote = _T('\0');
01582         for (Space = ptr + 1; Space < LastQuote; Space++)
01583         {
01584             if (_istspace(*Space))                         /* Rule 4 */
01585             {
01586                 if (!AlwaysStrip &&                        /* Rule 1 */
01587                     !_tcspbrk(ptr, _T("\"&<>@^|")) &&      /* Rules 2, 3 */
01588                     SearchForExecutable(ptr, commandline)) /* Rule 5 */
01589                 {
01590                     /* All conditions met: preserve both the quotes */
01591                     *LastQuote = _T('"');
01592                     _tcscpy(commandline, ptr - 1);
01593                     return;
01594                 }
01595                 break;
01596             }
01597         }
01598 
01599         /* The conditions were not met: remove both the
01600          * leading quote and the last quote */
01601         _tcscpy(commandline, ptr);
01602         _tcscpy(&commandline[LastQuote - ptr], LastQuote + 1);
01603         return;
01604     }
01605 
01606     /* No quotes; just copy */
01607     _tcscpy(commandline, ptr);
01608 }
01609 
01610 /*
01611  * set up global initializations and process parameters
01612  */
01613 static VOID
01614 Initialize()
01615 {
01616     HMODULE NtDllModule;
01617     TCHAR commandline[CMDLINE_LENGTH];
01618     TCHAR ModuleName[_MAX_PATH + 1];
01619     TCHAR lpBuffer[2];
01620     INT nExitCode;
01621 
01622     //INT len;
01623     TCHAR *ptr, *cmdLine, option = 0;
01624     BOOL AlwaysStrip = FALSE;
01625     BOOL AutoRun = TRUE;
01626 
01627     /* get version information */
01628     osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
01629     GetVersionEx (&osvi);
01630 
01631     /* Some people like to run ReactOS cmd.exe on Win98, it helps in the
01632      * build process. So don't link implicitly against ntdll.dll, load it
01633      * dynamically instead */
01634     NtDllModule = GetModuleHandle(TEXT("ntdll.dll"));
01635     if (NtDllModule != NULL)
01636     {
01637         NtQueryInformationProcessPtr = (NtQueryInformationProcessProc)GetProcAddress(NtDllModule, "NtQueryInformationProcess");
01638         NtReadVirtualMemoryPtr = (NtReadVirtualMemoryProc)GetProcAddress(NtDllModule, "NtReadVirtualMemory");
01639     }
01640 
01641     InitLocale ();
01642 
01643     /* get default input and output console handles */
01644     hOut = GetStdHandle (STD_OUTPUT_HANDLE);
01645     hIn  = GetStdHandle (STD_INPUT_HANDLE);
01646 
01647     /* Set EnvironmentVariable PROMPT if it does not exists any env value.
01648        for you can change the EnvirommentVariable for prompt before cmd start
01649        this patch are not 100% right, if it does not exists a PROMPT value cmd should use
01650        $P$G as defualt not set EnvirommentVariable PROMPT to $P$G if it does not exists */
01651     if (GetEnvironmentVariable(_T("PROMPT"),lpBuffer, sizeof(lpBuffer) / sizeof(lpBuffer[0])) == 0)
01652         SetEnvironmentVariable (_T("PROMPT"), _T("$P$G"));
01653 
01654 #ifdef FEATURE_DIR_STACK
01655     /* initialize directory stack */
01656     InitDirectoryStack ();
01657 #endif
01658 
01659 #ifdef FEATURE_HISTORY
01660     /*initialize history*/
01661     InitHistory();
01662 #endif
01663 
01664     /* Set COMSPEC environment variable */
01665     if (0 != GetModuleFileName (NULL, ModuleName, _MAX_PATH + 1))
01666     {
01667         ModuleName[_MAX_PATH] = _T('\0');
01668         SetEnvironmentVariable (_T("COMSPEC"), ModuleName);
01669     }
01670 
01671     /* add ctrl break handler */
01672     AddBreakHandler ();
01673 
01674 
01675     SetConsoleMode (hIn, ENABLE_PROCESSED_INPUT);
01676 
01677     cmdLine = GetCommandLine();
01678     TRACE ("[command args: %s]\n", debugstr_aw(cmdLine));
01679 
01680     for (ptr = cmdLine; *ptr; ptr++)
01681     {
01682         if (*ptr == _T('/'))
01683         {
01684             option = _totupper(ptr[1]);
01685             if (option == _T('?'))
01686             {
01687                 ConOutResPaging(TRUE,STRING_CMD_HELP8);
01688                 nErrorLevel = 1;
01689                 bExit = TRUE;
01690                 return;
01691             }
01692             else if (option == _T('P'))
01693             {
01694                 if (!IsExistingFile (_T("\\autoexec.bat")))
01695                 {
01696 #ifdef INCLUDE_CMD_DATE
01697                     cmd_date (_T(""));
01698 #endif
01699 #ifdef INCLUDE_CMD_TIME
01700                     cmd_time (_T(""));
01701 #endif
01702                 }
01703                 else
01704                 {
01705                     ParseCommandLine (_T("\\autoexec.bat"));
01706                 }
01707                 bCanExit = FALSE;
01708             }
01709             else if (option == _T('A'))
01710             {
01711                 bUnicodeOutput = FALSE;
01712             }
01713             else if (option == _T('C') || option == _T('K') || option == _T('R'))
01714             {
01715                 /* Remainder of command line is a command to be run */
01716                 break;
01717             }
01718             else if (option == _T('D'))
01719             {
01720                 AutoRun = FALSE;
01721             }
01722             else if (option == _T('Q'))
01723             {
01724                 bDisableBatchEcho = TRUE;
01725             }
01726             else if (option == _T('S'))
01727             {
01728                 AlwaysStrip = TRUE;
01729             }
01730 #ifdef INCLUDE_CMD_COLOR
01731             else if (!_tcsnicmp(ptr, _T("/T:"), 3))
01732             {
01733                 /* process /t (color) argument */
01734                 wDefColor = (WORD)_tcstoul(&ptr[3], &ptr, 16);
01735                 SetScreenColor(wDefColor, TRUE);
01736             }
01737 #endif
01738             else if (option == _T('U'))
01739             {
01740                 bUnicodeOutput = TRUE;
01741             }
01742             else if (option == _T('V'))
01743             {
01744                 bDelayedExpansion = _tcsnicmp(&ptr[2], _T(":OFF"), 4);
01745             }
01746         }
01747     }
01748 
01749     if (!*ptr)
01750     {
01751         /* If neither /C or /K was given, display a simple version string */
01752         ConOutResPrintf(STRING_REACTOS_VERSION,
01753             _T(KERNEL_RELEASE_STR),
01754             _T(KERNEL_VERSION_BUILD_STR));
01755         ConOutPuts(_T("(C) Copyright 1998-") _T(COPYRIGHT_YEAR) _T(" ReactOS Team.\n"));
01756     }
01757 
01758     if (AutoRun)
01759     {
01760         ExecuteAutoRunFile(HKEY_LOCAL_MACHINE);
01761         ExecuteAutoRunFile(HKEY_CURRENT_USER);
01762     }
01763 
01764     if (*ptr)
01765     {
01766         /* Do the /C or /K command */
01767         GetCmdLineCommand(commandline, &ptr[2], AlwaysStrip);
01768         nExitCode = ParseCommandLine(commandline);
01769         if (option != _T('K'))
01770         {
01771             nErrorLevel = nExitCode;
01772             bExit = TRUE;
01773         }
01774     }
01775 }
01776 
01777 
01778 static VOID Cleanup()
01779 {
01780     /* run cmdexit.bat */
01781     if (IsExistingFile (_T("cmdexit.bat")))
01782     {
01783         ConErrResPuts(STRING_CMD_ERROR5);
01784 
01785         ParseCommandLine (_T("cmdexit.bat"));
01786     }
01787     else if (IsExistingFile (_T("\\cmdexit.bat")))
01788     {
01789         ConErrResPuts (STRING_CMD_ERROR5);
01790         ParseCommandLine (_T("\\cmdexit.bat"));
01791     }
01792 
01793 #ifdef FEATURE_DIECTORY_STACK
01794     /* destroy directory stack */
01795     DestroyDirectoryStack ();
01796 #endif
01797 
01798 #ifdef FEATURE_HISTORY
01799     CleanHistory();
01800 #endif
01801 
01802     /* free GetEnvVar's buffer */
01803     GetEnvVar(NULL);
01804 
01805     /* remove ctrl break handler */
01806     RemoveBreakHandler ();
01807     SetConsoleMode( GetStdHandle( STD_INPUT_HANDLE ),
01808             ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT );
01809     DeleteCriticalSection(&ChildProcessRunningLock);
01810 }
01811 
01812 /*
01813  * main function
01814  */
01815 int cmd_main (int argc, const TCHAR *argv[])
01816 {
01817     HANDLE hConsole;
01818     TCHAR startPath[MAX_PATH];
01819     CONSOLE_SCREEN_BUFFER_INFO Info;
01820 
01821     InitializeCriticalSection(&ChildProcessRunningLock);
01822     lpOriginalEnvironment = DuplicateEnvironment();
01823 
01824     GetCurrentDirectory(MAX_PATH,startPath);
01825     _tchdir(startPath);
01826 
01827     SetFileApisToOEM();
01828     InputCodePage= 0;
01829     OutputCodePage = 0;
01830 
01831     hConsole = CreateFile(_T("CONOUT$"), GENERIC_READ|GENERIC_WRITE,
01832         FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
01833         OPEN_EXISTING, 0, NULL);
01834     if (hConsole != INVALID_HANDLE_VALUE)
01835     {
01836         if (!GetConsoleScreenBufferInfo(hConsole, &Info))
01837         {
01838             ConErrFormatMessage(GetLastError());
01839             return(1);
01840         }
01841         wDefColor = Info.wAttributes;
01842         CloseHandle(hConsole);
01843     }
01844 
01845     InputCodePage= GetConsoleCP();
01846     OutputCodePage = GetConsoleOutputCP();
01847     CMD_ModuleHandle = GetModuleHandle(NULL);
01848 
01849     /* check switches on command-line */
01850     Initialize();
01851 
01852     /* call prompt routine */
01853     ProcessInput();
01854 
01855     /* do the cleanup */
01856     Cleanup();
01857 
01858     cmd_free(lpOriginalEnvironment);
01859 
01860     cmd_exit(nErrorLevel);
01861     return(nErrorLevel);
01862 }
01863 
01864 /* EOF */

Generated on Sun May 27 2012 04:18:10 for ReactOS by doxygen 1.7.6.1

ReactOS is a registered trademark or a trademark of ReactOS Foundation in the United States and other countries.