BOOL bCtrlBreak = FALSE; /* Ctrl-Break or Ctrl-C hit */
BOOL bIgnoreEcho = FALSE; /* Set this to TRUE to prevent a newline, when executing a command */
INT nErrorLevel = 0; /* Errorlevel of last launched external program */
-BOOL bChildProcessRunning = FALSE;
+CRITICAL_SECTION ChildProcessRunningLock;
+BOOL bUnicodeOutput = FALSE;
+BOOL bDisableBatchEcho = FALSE;
+BOOL bDelayedExpansion = FALSE;
DWORD dwChildProcessId = 0;
OSVERSIONINFO osvi;
HANDLE hIn;
HANDLE hOut;
-HANDLE hConsole;
+LPTSTR lpOriginalEnvironment;
HANDLE CMD_ModuleHandle;
-HMODULE NtDllModule;
static NtQueryInformationProcessProc NtQueryInformationProcessPtr = NULL;
static NtReadVirtualMemoryProc NtReadVirtualMemoryPtr = NULL;
#ifdef INCLUDE_CMD_COLOR
-WORD wColor; /* current color */
WORD wDefColor; /* default color */
#endif
* insert commas into a number
*/
INT
-ConvertULargeInteger (ULARGE_INTEGER num, LPTSTR des, INT len, BOOL bPutSeperator)
+ConvertULargeInteger(ULONGLONG num, LPTSTR des, INT len, BOOL bPutSeperator)
{
- TCHAR temp[32];
+ TCHAR temp[39]; /* maximum length with nNumberGroups == 1 */
UINT n, iTarget;
if (len <= 1)
return 0;
- if (num.QuadPart == 0)
- {
- des[0] = _T('0');
- des[1] = _T('\0');
- return 1;
- }
-
n = 0;
iTarget = nNumberGroups;
if (!nNumberGroups)
bPutSeperator = FALSE;
- while (num.QuadPart > 0)
+ do
{
if (iTarget == n && bPutSeperator)
{
iTarget += nNumberGroups + 1;
- temp[31 - n++] = cThousandSeparator;
+ temp[38 - n++] = cThousandSeparator;
}
- temp[31 - n++] = (TCHAR)(num.QuadPart % 10) + _T('0');
- num.QuadPart /= 10;
- }
+ temp[38 - n++] = (TCHAR)(num % 10) + _T('0');
+ num /= 10;
+ } while (num > 0);
if (n > len-1)
n = len-1;
- memcpy(des, temp + 32 - n, n * sizeof(TCHAR));
+ memcpy(des, temp + 39 - n, n * sizeof(TCHAR));
des[n] = _T('\0');
return n;
}
-/*
- * is character a delimeter when used on first word?
- *
- */
-static BOOL IsDelimiter (TCHAR c)
-{
- return (c == _T('/') || c == _T('=') || c == _T('\0') || _istspace (c));
-}
-
/*
* Is a process a console process?
*/
return TRUE;
}
- return IMAGE_SUBSYSTEM_WINDOWS_CUI == ProcessPeb.ImageSubSystem;
+ return IMAGE_SUBSYSTEM_WINDOWS_CUI == ProcessPeb.ImageSubsystem;
}
#ifdef _UNICODE
-#define SHELLEXECUTETEXT "ShellExecuteW"
+#define SHELLEXECUTETEXT "ShellExecuteExW"
#else
-#define SHELLEXECUTETEXT "ShellExecuteA"
+#define SHELLEXECUTETEXT "ShellExecuteExA"
#endif
-typedef HINSTANCE (WINAPI *MYEX)(
- HWND hwnd,
- LPCTSTR lpOperation,
- LPCTSTR lpFile,
- LPCTSTR lpParameters,
- LPCTSTR lpDirectory,
- INT nShowCmd
-);
-
-
+typedef BOOL (WINAPI *MYEX)(LPSHELLEXECUTEINFO lpExecInfo);
-static BOOL RunFile(LPTSTR filename)
+HANDLE RunFile(DWORD flags, LPTSTR filename, LPTSTR params,
+ LPTSTR directory, INT show)
{
+ SHELLEXECUTEINFO sei;
HMODULE hShell32;
MYEX hShExt;
- HINSTANCE ret;
+ BOOL ret;
TRACE ("RunFile(%s)\n", debugstr_aw(filename));
hShell32 = LoadLibrary(_T("SHELL32.DLL"));
if (!hShell32)
{
WARN ("RunFile: couldn't load SHELL32.DLL!\n");
- return FALSE;
+ return NULL;
}
hShExt = (MYEX)(FARPROC)GetProcAddress(hShell32, SHELLEXECUTETEXT);
if (!hShExt)
{
- WARN ("RunFile: couldn't find ShellExecuteA/W in SHELL32.DLL!\n");
+ WARN ("RunFile: couldn't find ShellExecuteExA/W in SHELL32.DLL!\n");
FreeLibrary(hShell32);
- return FALSE;
+ return NULL;
}
- TRACE ("RunFile: ShellExecuteA/W is at %x\n", hShExt);
+ TRACE ("RunFile: ShellExecuteExA/W is at %x\n", hShExt);
- ret = (hShExt)(NULL, _T("open"), filename, NULL, NULL, SW_SHOWNORMAL);
+ memset(&sei, 0, sizeof sei);
+ sei.cbSize = sizeof sei;
+ sei.fMask = flags;
+ sei.lpFile = filename;
+ sei.lpParameters = params;
+ sei.lpDirectory = directory;
+ sei.nShow = show;
+ ret = hShExt(&sei);
- TRACE ("RunFile: ShellExecuteA/W returned %p\n", (DWORD_PTR)ret);
+ TRACE ("RunFile: ShellExecuteExA/W returned 0x%p\n", ret);
FreeLibrary(hShell32);
- return (((DWORD_PTR)ret) > 32);
+ return ret ? sei.hProcess : NULL;
}
/*
* This command (in first) was not found in the command table
*
- * Full - whole command line
+ * Full - buffer to hold whole command line
* First - first word on command line
* Rest - rest of command line
*/
-static BOOL
-Execute (LPTSTR Full, LPTSTR First, LPTSTR Rest)
+static INT
+Execute (LPTSTR Full, LPTSTR First, LPTSTR Rest, PARSED_COMMAND *Cmd)
{
- TCHAR *szFullName=NULL;
- TCHAR *first = NULL;
- TCHAR *rest = NULL;
- TCHAR *full = NULL;
- TCHAR *dot = NULL;
+ TCHAR szFullName[MAX_PATH];
+ TCHAR *first, *rest, *dot;
TCHAR szWindowTitle[MAX_PATH];
DWORD dwExitCode = 0;
+ TCHAR *FirstEnd;
- TRACE ("Execute: \'%s\' \'%s\'\n", debugstr_aw(first), debugstr_aw(rest));
-
- /* we need biger buffer that First, Rest, Full are already
- need rewrite some code to use cmd_realloc when it need instead
- of add 512bytes extra */
-
- first = cmd_alloc ( (_tcslen(First) + 512) * sizeof(TCHAR));
- if (first == NULL)
- {
- error_out_of_memory();
- nErrorLevel = 1;
- return FALSE;
- }
-
- rest = cmd_alloc ( (_tcslen(Rest) + 512) * sizeof(TCHAR));
- if (rest == NULL)
- {
- cmd_free (first);
- error_out_of_memory();
- nErrorLevel = 1;
- return FALSE;
- }
-
- full = cmd_alloc ( (_tcslen(Full) + 512) * sizeof(TCHAR));
- if (full == NULL)
- {
- cmd_free (first);
- cmd_free (rest);
- error_out_of_memory();
- nErrorLevel = 1;
- return FALSE;
- }
-
- szFullName = cmd_alloc ( (_tcslen(Full) + 512) * sizeof(TCHAR));
- if (full == NULL)
- {
- cmd_free (first);
- cmd_free (rest);
- cmd_free (full);
- error_out_of_memory();
- nErrorLevel = 1;
- return FALSE;
- }
-
+ TRACE ("Execute: \'%s\' \'%s\'\n", debugstr_aw(First), debugstr_aw(Rest));
/* Though it was already parsed once, we have a different set of rules
for parsing before we pass to CreateProccess */
- if(!_tcschr(Full,_T('\"')))
+ if (First[0] == _T('/') || (First[0] && First[1] == _T(':')))
{
- _tcscpy(first,First);
- _tcscpy(rest,Rest);
- _tcscpy(full,Full);
+ /* Use the entire first word as the program name (no change) */
+ FirstEnd = First + _tcslen(First);
}
else
{
- UINT i = 0;
+ /* If present in the first word, spaces and ,;=/ end the program
+ * name and become the beginning of its parameters. */
BOOL bInside = FALSE;
- rest[0] = _T('\0');
- full[0] = _T('\0');
- first[0] = _T('\0');
- _tcscpy(first,Full);
- /* find the end of the command and start of the args */
- for(i = 0; i < _tcslen(first); i++)
+ for (FirstEnd = First; *FirstEnd; FirstEnd++)
{
- if(!_tcsncmp(&first[i], _T("\""), 1))
- bInside = !bInside;
- if(!_tcsncmp(&first[i], _T(" "), 1) && !bInside)
- {
- _tcscpy(rest,&first[i]);
- first[i] = _T('\0');
+ if (!bInside && (_istspace(*FirstEnd) || _tcschr(_T(",;=/"), *FirstEnd)))
break;
- }
-
+ bInside ^= *FirstEnd == _T('"');
}
- i = 0;
- /* remove any slashes */
- while(i < _tcslen(first))
- {
- if(first[i] == _T('\"'))
- memmove(&first[i],&first[i + 1], _tcslen(&first[i]) * sizeof(TCHAR));
- else
- i++;
- }
- /* Drop quotes around it just in case there is a space */
- _tcscpy(full,_T("\""));
- _tcscat(full,first);
- _tcscat(full,_T("\" "));
- _tcscat(full,rest);
}
+ /* Copy the new first/rest into the buffer */
+ first = Full;
+ rest = &Full[FirstEnd - First + 1];
+ _tcscpy(rest, FirstEnd);
+ _tcscat(rest, Rest);
+ *FirstEnd = _T('\0');
+ _tcscpy(first, First);
+
/* check for a drive change */
if ((_istalpha (first[0])) && (!_tcscmp (first + 1, _T(":"))))
{
}
if (!working) ConErrResPuts (STRING_FREE_ERROR1);
-
- cmd_free (first);
- cmd_free (rest);
- cmd_free (full);
- cmd_free (szFullName);
- nErrorLevel = 1;
- return working;
+ return !working;
}
/* get the PATH environment variable and parse it */
/* search the PATH environment variable for the binary */
- if (!SearchForExecutable (first, szFullName))
+ StripQuotes(First);
+ if (!SearchForExecutable(First, szFullName))
{
- error_bad_command ();
- cmd_free (first);
- cmd_free (rest);
- cmd_free (full);
- cmd_free (szFullName);
- nErrorLevel = 1;
- return FALSE;
-
+ error_bad_command(first);
+ return 1;
}
GetConsoleTitle (szWindowTitle, MAX_PATH);
dot = _tcsrchr (szFullName, _T('.'));
if (dot && (!_tcsicmp (dot, _T(".bat")) || !_tcsicmp (dot, _T(".cmd"))))
{
+ while (*rest == _T(' '))
+ rest++;
TRACE ("[BATCH: %s %s]\n", debugstr_aw(szFullName), debugstr_aw(rest));
- Batch (szFullName, first, rest, FALSE);
+ dwExitCode = Batch(szFullName, first, rest, Cmd);
}
else
{
PROCESS_INFORMATION prci;
STARTUPINFO stui;
- TRACE ("[EXEC: %s %s]\n", debugstr_aw(full), debugstr_aw(rest));
- /* build command line for CreateProcess() */
+ /* build command line for CreateProcess(): first + " " + rest */
+ if (*rest)
+ rest[-1] = _T(' ');
+
+ TRACE ("[EXEC: %s]\n", debugstr_aw(Full));
/* fill startup info */
memset (&stui, 0, sizeof (STARTUPINFO));
ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT );
if (CreateProcess (szFullName,
- full,
+ Full,
NULL,
NULL,
TRUE,
&stui,
&prci))
+ {
+ CloseHandle(prci.hThread);
+ }
+ else
+ {
+ // See if we can run this with ShellExecute() ie myfile.xls
+ prci.hProcess = RunFile(SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE,
+ szFullName,
+ rest,
+ NULL,
+ SW_SHOWNORMAL);
+ }
+
+ if (prci.hProcess != NULL)
{
if (IsConsoleProcess(prci.hProcess))
{
- /* FIXME: Protect this with critical section */
- bChildProcessRunning = TRUE;
+ EnterCriticalSection(&ChildProcessRunningLock);
dwChildProcessId = prci.dwProcessId;
WaitForSingleObject (prci.hProcess, INFINITE);
- /* FIXME: Protect this with critical section */
- bChildProcessRunning = FALSE;
+ LeaveCriticalSection(&ChildProcessRunningLock);
GetExitCodeProcess (prci.hProcess, &dwExitCode);
nErrorLevel = (INT)dwExitCode;
}
- else
- {
- nErrorLevel = 0;
- }
- CloseHandle (prci.hThread);
CloseHandle (prci.hProcess);
}
else
{
- TRACE ("[ShellExecute: %s]\n", debugstr_aw(full));
- // See if we can run this with ShellExecute() ie myfile.xls
- if (!RunFile(full))
- {
- TRACE ("[ShellExecute failed!: %s]\n", debugstr_aw(full));
- error_bad_command ();
- nErrorLevel = 1;
- }
- else
- {
- nErrorLevel = 0;
- }
+ TRACE ("[ShellExecute failed!: %s]\n", debugstr_aw(Full));
+ error_bad_command (first);
+ dwExitCode = 1;
}
+
// restore console mode
SetConsoleMode (
GetStdHandle( STD_INPUT_HANDLE ),
OutputCodePage = GetConsoleOutputCP();
SetConsoleTitle (szWindowTitle);
- cmd_free(first);
- cmd_free(rest);
- cmd_free(full);
- cmd_free (szFullName);
- return nErrorLevel == 0;
+ return dwExitCode;
}
* 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
- *
+ * first - first word on command line
+ * rest - rest of command line
*/
-BOOL
-DoCommand (LPTSTR line)
+INT
+DoCommand(LPTSTR first, LPTSTR rest, PARSED_COMMAND *Cmd)
{
- TCHAR *com = NULL; /* the first word in the command */
- TCHAR *cp = NULL;
- LPTSTR cstart;
- LPTSTR rest; /* pointer to the rest of the command line */
+ TCHAR *com;
+ TCHAR *cp;
+ LPTSTR param; /* pointer to command's parameters */
INT cl;
LPCOMMAND cmdptr;
- BOOL ret = TRUE;
+ BOOL nointernal = FALSE;
+ INT ret;
- TRACE ("DoCommand: (\'%s\')\n", debugstr_aw(line));
+ TRACE ("DoCommand: (\'%s\' \'%s\')\n", debugstr_aw(first), debugstr_aw(rest));
- com = cmd_alloc( (_tcslen(line) +512)*sizeof(TCHAR) );
+ /* full command line */
+ com = cmd_alloc((_tcslen(first) + _tcslen(rest) + 2) * sizeof(TCHAR));
if (com == NULL)
{
error_out_of_memory();
- return FALSE;
+ return 1;
}
- cp = com;
- /* Skip over initial white space */
- while (_istspace (*line))
- line++;
- rest = line;
-
- cstart = rest;
+ /* If present in the first word, these characters end the name of an
+ * internal command and become the beginning of its parameters. */
+ cp = first + _tcscspn(first, _T("\t +,/;=[]"));
- /* Anything to do ? */
- if (*rest)
+ for (cl = 0; cl < (cp - first); cl++)
{
- if (*rest == _T('"'))
+ /* These characters do it too, but if one of them is present,
+ * then we check to see if the word is a file name and skip
+ * checking for internal commands if so.
+ * This allows running programs with names like "echo.exe" */
+ if (_tcschr(_T(".:\\"), first[cl]))
{
- /* treat quoted words specially */
-
- rest++;
-
- while(*rest != _T('\0') && *rest != _T('"'))
- *cp++ = _totlower (*rest++);
- if (*rest == _T('"'))
- rest++;
+ TCHAR tmp = *cp;
+ *cp = _T('\0');
+ nointernal = IsExistingFile(first);
+ *cp = tmp;
+ break;
}
- else
- {
- while (!IsDelimiter (*rest))
- *cp++ = _totlower (*rest++);
- }
-
-
- /* Terminate first word */
- *cp = _T('\0');
+ }
- /* Do not limit commands to MAX_PATH */
- /*
- if(_tcslen(com) > MAX_PATH)
+ /* Scan internal command table */
+ for (cmdptr = cmds; !nointernal && cmdptr->name; cmdptr++)
+ {
+ if (!_tcsnicmp(first, cmdptr->name, cl) && cmdptr->name[cl] == _T('\0'))
{
- error_bad_command();
+ _tcscpy(com, first);
+ _tcscat(com, rest);
+ param = &com[cl];
+
+ /* Skip over whitespace to rest of line, exclude 'echo' command */
+ if (_tcsicmp(cmdptr->name, _T("echo")) != 0)
+ while (_istspace(*param))
+ param++;
+ ret = cmdptr->func(param);
cmd_free(com);
- return;
- }
- */
-
- /* Skip over whitespace to rest of line, exclude 'echo' command */
- if (_tcsicmp (com, _T("echo")))
- {
- while (_istspace (*rest))
- rest++;
- }
-
- /* Scan internal command table */
- for (cmdptr = cmds;; cmdptr++)
- {
- /* If end of table execute ext cmd */
- if (cmdptr->name == NULL)
- {
- ret = Execute (line, com, rest);
- break;
- }
-
- if (!_tcscmp (com, cmdptr->name))
- {
- cmdptr->func (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...*/
-
- /* Call with new rest */
- cmdptr->func (cstart + cl);
- break;
- }
+ return ret;
}
}
+
+ ret = Execute(com, first, rest, Cmd);
cmd_free(com);
return ret;
}
* full input/output redirection and piping are supported
*/
-VOID ParseCommandLine (LPTSTR cmd)
+INT ParseCommandLine (LPTSTR cmd)
{
+ INT Ret = 0;
PARSED_COMMAND *Cmd = ParseCommand(cmd);
if (Cmd)
{
- ExecuteCommand(Cmd);
+ Ret = ExecuteCommand(Cmd);
FreeCommand(Cmd);
}
+ return Ret;
}
-static VOID
-ExecutePipeline(PARSED_COMMAND *Cmd)
+/* Execute a command without waiting for it to finish. If it's an internal
+ * command or batch file, we must create a new cmd.exe process to handle it.
+ * TODO: For now, this just always creates a cmd.exe process.
+ * This works, but is inefficient for running external programs,
+ * which could just be run directly. */
+static HANDLE
+ExecuteAsync(PARSED_COMMAND *Cmd)
{
-#ifdef FEATURE_REDIRECTION
- TCHAR szTempPath[MAX_PATH] = _T(".\\");
- TCHAR szFileName[2][MAX_PATH] = {_T(""), _T("")};
- HANDLE hFile[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
- INT Length;
- UINT Attributes;
- HANDLE hOldConIn;
- HANDLE hOldConOut;
-#endif /* FEATURE_REDIRECTION */
-
- //TRACE ("ParseCommandLine: (\'%s\')\n", debugstr_aw(s));
-
-#ifdef FEATURE_REDIRECTION
- /* find the temp path to store temporary files */
- Length = GetTempPath (MAX_PATH, szTempPath);
- if (Length > 0 && Length < MAX_PATH)
+ TCHAR CmdPath[MAX_PATH];
+ TCHAR CmdParams[CMDLINE_LENGTH], *ParamsEnd;
+ STARTUPINFO stui;
+ PROCESS_INFORMATION prci;
+
+ /* Get the path to cmd.exe */
+ GetModuleFileName(NULL, CmdPath, MAX_PATH);
+
+ /* Build the parameter string to pass to cmd.exe */
+ ParamsEnd = _stpcpy(CmdParams, _T("/S/D/C\""));
+ ParamsEnd = Unparse(Cmd, ParamsEnd, &CmdParams[CMDLINE_LENGTH - 2]);
+ if (!ParamsEnd)
{
- Attributes = GetFileAttributes(szTempPath);
- if (Attributes == 0xffffffff ||
- !(Attributes & FILE_ATTRIBUTE_DIRECTORY))
- {
- Length = 0;
- }
+ error_out_of_memory();
+ return NULL;
}
- if (Length == 0 || Length >= MAX_PATH)
+ _tcscpy(ParamsEnd, _T("\""));
+
+ memset(&stui, 0, sizeof stui);
+ stui.cb = sizeof(STARTUPINFO);
+ if (!CreateProcess(CmdPath, CmdParams, NULL, NULL, TRUE, 0,
+ NULL, NULL, &stui, &prci))
{
- _tcscpy(szTempPath, _T(".\\"));
+ ErrorMessage(GetLastError(), NULL);
+ return NULL;
}
- if (szTempPath[_tcslen (szTempPath) - 1] != _T('\\'))
- _tcscat (szTempPath, _T("\\"));
-
- /* Set up the initial conditions ... */
- /* preserve STDIN and STDOUT handles */
- hOldConIn = GetStdHandle (STD_INPUT_HANDLE);
- hOldConOut = GetStdHandle (STD_OUTPUT_HANDLE);
- /* Now do all but the last pipe command */
- *szFileName[0] = _T('\0');
- hFile[0] = INVALID_HANDLE_VALUE;
+ CloseHandle(prci.hThread);
+ return prci.hProcess;
+}
- while (Cmd->Type == C_PIPE)
+static VOID
+ExecutePipeline(PARSED_COMMAND *Cmd)
+{
+#ifdef FEATURE_REDIRECTION
+ HANDLE hInput = NULL;
+ HANDLE hOldConIn = GetStdHandle(STD_INPUT_HANDLE);
+ HANDLE hOldConOut = GetStdHandle(STD_OUTPUT_HANDLE);
+ HANDLE hProcess[MAXIMUM_WAIT_OBJECTS];
+ INT nProcesses = 0;
+ DWORD dwExitCode;
+
+ /* Do all but the last pipe command */
+ do
{
- SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
-
- /* Create unique temporary file name */
- GetTempFileName (szTempPath, _T("CMD"), 0, szFileName[1]);
-
- /* we need make sure the LastError msg is zero before calling CreateFile */
- SetLastError(0);
-
- /* Set current stdout to temporary file */
- hFile[1] = CreateFile (szFileName[1], GENERIC_WRITE, 0, &sa,
- TRUNCATE_EXISTING, FILE_ATTRIBUTE_TEMPORARY, NULL);
-
- if (hFile[1] == INVALID_HANDLE_VALUE)
+ HANDLE hPipeRead, hPipeWrite;
+ if (nProcesses > (MAXIMUM_WAIT_OBJECTS - 2))
{
- ConErrResPrintf(STRING_CMD_ERROR2);
- return;
+ error_too_many_parameters(_T("|"));
+ goto failed;
}
- SetStdHandle (STD_OUTPUT_HANDLE, hFile[1]);
-
- ExecuteCommand(Cmd->Subcommands);
-
- /* close stdout file */
- SetStdHandle (STD_OUTPUT_HANDLE, hOldConOut);
- if ((hFile[1] != INVALID_HANDLE_VALUE) && (hFile[1] != hOldConOut))
+ /* Create the pipe that this process will write into.
+ * Make the handles non-inheritable initially, because this
+ * process shouldn't inherit the reading handle. */
+ if (!CreatePipe(&hPipeRead, &hPipeWrite, NULL, 0))
{
- CloseHandle (hFile[1]);
- hFile[1] = INVALID_HANDLE_VALUE;
+ error_no_pipe();
+ goto failed;
}
- /* 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');
- }
+ /* The writing side of the pipe is STDOUT for this process */
+ SetHandleInformation(hPipeWrite, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
+ SetStdHandle(STD_OUTPUT_HANDLE, hPipeWrite);
- /* copy stdout file name to stdin file name */
- _tcscpy (szFileName[0], szFileName[1]);
- *szFileName[1] = _T('\0');
+ /* Execute it (error check is done later for easier cleanup) */
+ hProcess[nProcesses] = ExecuteAsync(Cmd->Subcommands);
+ CloseHandle(hPipeWrite);
+ if (hInput)
+ CloseHandle(hInput);
- /* we need make sure the LastError msg is zero before calling CreateFile */
- SetLastError(0);
+ /* The reading side of the pipe will be STDIN for the next process */
+ SetHandleInformation(hPipeRead, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
+ SetStdHandle(STD_INPUT_HANDLE, hPipeRead);
+ hInput = hPipeRead;
- /* 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]);
+ if (!hProcess[nProcesses])
+ goto failed;
+ nProcesses++;
Cmd = Cmd->Subcommands->Next;
- }
+ } while (Cmd->Type == C_PIPE);
- /* Now set up the end conditions... */
+ /* The last process uses the original STDOUT */
SetStdHandle(STD_OUTPUT_HANDLE, hOldConOut);
-
-#endif
-
- /* process final command */
- ExecuteCommand(Cmd);
-
-#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))
+ hProcess[nProcesses] = ExecuteAsync(Cmd);
+ if (!hProcess[nProcesses])
+ goto failed;
+ nProcesses++;
+ CloseHandle(hInput);
+ SetStdHandle(STD_INPUT_HANDLE, hOldConIn);
+
+ /* Wait for all processes to complete */
+ EnterCriticalSection(&ChildProcessRunningLock);
+ WaitForMultipleObjects(nProcesses, hProcess, TRUE, INFINITE);
+ LeaveCriticalSection(&ChildProcessRunningLock);
+
+ /* Use the exit code of the last process in the pipeline */
+ GetExitCodeProcess(hProcess[nProcesses - 1], &dwExitCode);
+ nErrorLevel = (INT)dwExitCode;
+
+ while (--nProcesses >= 0)
+ CloseHandle(hProcess[nProcesses]);
+ return;
+
+failed:
+ if (hInput)
+ CloseHandle(hInput);
+ while (--nProcesses >= 0)
{
- /* 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');
+ TerminateProcess(hProcess[nProcesses], 0);
+ CloseHandle(hProcess[nProcesses]);
}
-
- /* 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
- {
- WARN ("Can't restore STDIN! Is invalid!!\n", out);
- }
-#endif /* buggy implementation */
-
-
- if (hOldConIn != INVALID_HANDLE_VALUE)
- {
- HANDLE hIn = GetStdHandle (STD_INPUT_HANDLE);
- SetStdHandle (STD_INPUT_HANDLE, hOldConIn);
- if (hIn == INVALID_HANDLE_VALUE)
- {
- WARN ("Previous STDIN is invalid!!\n");
- }
- 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
- {
- WARN ("hFile[0] and hIn dont match!!!\n");
- }
- }
- }
- }
-#endif /* FEATURE_REDIRECTION */
+ SetStdHandle(STD_INPUT_HANDLE, hOldConIn);
+ SetStdHandle(STD_OUTPUT_HANDLE, hOldConOut);
+#endif
}
-BOOL
+INT
ExecuteCommand(PARSED_COMMAND *Cmd)
{
- BOOL bNewBatch = TRUE;
PARSED_COMMAND *Sub;
- BOOL Success = TRUE;
+ LPTSTR First, Rest;
+ INT Ret = 0;
if (!PerformRedirection(Cmd->Redirections))
- return FALSE;
+ return 1;
switch (Cmd->Type)
{
case C_COMMAND:
- if(bc)
- bNewBatch = FALSE;
-
- Success = DoCommand(Cmd->CommandLine);
-
- if(bNewBatch && bc)
- AddBatchRedirection(&Cmd->Redirections);
+ Ret = 1;
+ First = DoDelayedExpansion(Cmd->Command.First);
+ if (First)
+ {
+ Rest = DoDelayedExpansion(Cmd->Command.Rest);
+ if (Rest)
+ {
+ Ret = DoCommand(First, Rest, Cmd);
+ cmd_free(Rest);
+ }
+ cmd_free(First);
+ }
break;
case C_QUIET:
case C_BLOCK:
case C_MULTI:
for (Sub = Cmd->Subcommands; Sub; Sub = Sub->Next)
- Success = ExecuteCommand(Sub);
+ Ret = ExecuteCommand(Sub);
break;
case C_IFFAILURE:
- case C_IFSUCCESS:
Sub = Cmd->Subcommands;
- Success = ExecuteCommand(Sub);
- if (Success == (Cmd->Type - C_IFFAILURE))
+ Ret = ExecuteCommand(Sub);
+ if (Ret != 0)
{
- Sub = Sub->Next;
- Success = ExecuteCommand(Sub);
+ nErrorLevel = Ret;
+ Ret = ExecuteCommand(Sub->Next);
}
break;
+ case C_IFSUCCESS:
+ Sub = Cmd->Subcommands;
+ Ret = ExecuteCommand(Sub);
+ if (Ret == 0)
+ Ret = ExecuteCommand(Sub->Next);
+ break;
case C_PIPE:
ExecutePipeline(Cmd);
break;
+ case C_IF:
+ Ret = ExecuteIf(Cmd);
+ break;
+ case C_FOR:
+ Ret = ExecuteFor(Cmd);
+ break;
}
UndoRedirection(Cmd->Redirections, NULL);
- return Success;
+ return Ret;
}
-BOOL
-GrowIfNecessary_dbg ( UINT needed, LPTSTR* ret, UINT* retlen, const char *file, int line )
-{
- if ( *ret && needed < *retlen )
- return TRUE;
- *retlen = needed;
- if ( *ret )
- cmd_free ( *ret );
-#ifdef _DEBUG_MEM
- *ret = (LPTSTR)cmd_alloc_dbg ( *retlen * sizeof(TCHAR), file, line );
-#else
- *ret = (LPTSTR)cmd_alloc ( *retlen * sizeof(TCHAR) );
-#endif
- if ( !*ret )
- SetLastError ( ERROR_OUTOFMEMORY );
- return *ret != NULL;
-}
-#define GrowIfNecessary(x, y, z) GrowIfNecessary_dbg(x, y, z, __FILE__, __LINE__)
-
-LPCTSTR
-GetEnvVarOrSpecial ( LPCTSTR varName )
+LPTSTR
+GetEnvVar(LPCTSTR varName)
{
static LPTSTR ret = NULL;
- static UINT retlen = 0;
UINT size;
- size = GetEnvironmentVariable ( varName, ret, retlen );
- if ( size > retlen )
+ cmd_free(ret);
+ ret = NULL;
+ size = GetEnvironmentVariable(varName, NULL, 0);
+ if (size > 0)
{
- if ( !GrowIfNecessary ( size, &ret, &retlen ) )
- return NULL;
- size = GetEnvironmentVariable ( varName, ret, retlen );
+ ret = cmd_alloc(size * sizeof(TCHAR));
+ if (ret != NULL)
+ GetEnvironmentVariable(varName, ret, size + 1);
}
- if ( size )
- return ret;
+ return ret;
+}
+
+LPCTSTR
+GetEnvVarOrSpecial(LPCTSTR varName)
+{
+ static TCHAR ret[MAX_PATH];
+
+ LPTSTR var = GetEnvVar(varName);
+ if (var)
+ return var;
/* env var doesn't exist, look for a "special" one */
/* %CD% */
if (_tcsicmp(varName,_T("cd")) ==0)
{
- size = GetCurrentDirectory ( retlen, ret );
- if ( size > retlen )
- {
- if ( !GrowIfNecessary ( size, &ret, &retlen ) )
- return NULL;
- size = GetCurrentDirectory ( retlen, ret );
- }
- if ( !size )
- return NULL;
+ GetCurrentDirectory(MAX_PATH, ret);
return ret;
}
/* %TIME% */
else if (_tcsicmp(varName,_T("time")) ==0)
{
- SYSTEMTIME t;
- if ( !GrowIfNecessary ( MAX_PATH, &ret, &retlen ) )
- return NULL;
- GetSystemTime(&t);
- _sntprintf ( ret, retlen, _T("%02d%c%02d%c%02d%c%02d"),
- t.wHour, cTimeSeparator, t.wMinute, cTimeSeparator,
- t.wSecond, cDecimalSeparator, t.wMilliseconds / 10);
- return ret;
+ return GetTimeString();
}
/* %DATE% */
else if (_tcsicmp(varName,_T("date")) ==0)
{
-
- if ( !GrowIfNecessary ( GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, NULL, 0), &ret, &retlen ) )
- return NULL;
-
- size = GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, ret, retlen);
-
- if ( !size )
- return NULL;
- return ret;
+ return GetDateString();
}
/* %RANDOM% */
else if (_tcsicmp(varName,_T("random")) ==0)
{
- if ( !GrowIfNecessary ( MAX_PATH, &ret, &retlen ) )
- return NULL;
/* Get random number */
_itot(rand(),ret,10);
return ret;
/* %CMDEXTVERSION% */
else if (_tcsicmp(varName,_T("cmdextversion")) ==0)
{
- if ( !GrowIfNecessary ( MAX_PATH, &ret, &retlen ) )
- return NULL;
/* Set version number to 2 */
_itot(2,ret,10);
return ret;
/* %ERRORLEVEL% */
else if (_tcsicmp(varName,_T("errorlevel")) ==0)
{
- if ( !GrowIfNecessary ( MAX_PATH, &ret, &retlen ) )
- return NULL;
_itot(nErrorLevel,ret,10);
return ret;
}
return NULL;
}
+/* Handle the %~var syntax */
+static LPTSTR
+GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, BOOL *))
+{
+ static const TCHAR ModifierTable[] = _T("dpnxfsatz");
+ enum {
+ M_DRIVE = 1, /* D: drive letter */
+ M_PATH = 2, /* P: path */
+ M_NAME = 4, /* N: filename */
+ M_EXT = 8, /* X: extension */
+ M_FULL = 16, /* F: full path (drive+path+name+ext) */
+ M_SHORT = 32, /* S: full path (drive+path+name+ext), use short names */
+ M_ATTR = 64, /* A: attributes */
+ M_TIME = 128, /* T: modification time */
+ M_SIZE = 256, /* Z: file size */
+ } Modifiers = 0;
+
+ TCHAR *Format, *FormatEnd;
+ TCHAR *PathVarName = NULL;
+ LPTSTR Variable;
+ TCHAR *VarEnd;
+ BOOL VariableIsParam0;
+ TCHAR FullPath[MAX_PATH];
+ TCHAR FixedPath[MAX_PATH], *Filename, *Extension;
+ HANDLE hFind;
+ WIN32_FIND_DATA w32fd;
+ TCHAR *In, *Out;
+
+ static TCHAR Result[CMDLINE_LENGTH];
+
+ /* There is ambiguity between modifier characters and FOR variables;
+ * the rule that cmd uses is to pick the longest possible match.
+ * For example, if there is a %n variable, then out of %~anxnd,
+ * %~anxn will be substituted rather than just %~an. */
+
+ /* First, go through as many modifier characters as possible */
+ FormatEnd = Format = *pFormat;
+ while (*FormatEnd && _tcschr(ModifierTable, _totlower(*FormatEnd)))
+ FormatEnd++;
+
+ if (*FormatEnd == _T('$'))
+ {
+ /* $PATH: syntax */
+ PathVarName = FormatEnd + 1;
+ FormatEnd = _tcschr(PathVarName, _T(':'));
+ if (!FormatEnd)
+ return NULL;
+ /* Must be immediately followed by the variable */
+ Variable = GetVar(*++FormatEnd, &VariableIsParam0);
+ if (!Variable)
+ return NULL;
+ }
+ else
+ {
+ /* Backtrack if necessary to get a variable name match */
+ while (!(Variable = GetVar(*FormatEnd, &VariableIsParam0)))
+ {
+ if (FormatEnd == Format)
+ return NULL;
+ FormatEnd--;
+ }
+ }
-LPCTSTR
-GetBatchVar ( LPCTSTR varName, UINT* varNameLen )
-{
- static LPTSTR ret = NULL;
- static UINT retlen = 0;
- DWORD len;
+ for (; Format < FormatEnd && *Format != _T('$'); Format++)
+ Modifiers |= 1 << (_tcschr(ModifierTable, _totlower(*Format)) - ModifierTable);
- *varNameLen = 1;
+ *pFormat = FormatEnd + 1;
- switch ( *varName )
+ /* Exclude the leading and trailing quotes */
+ VarEnd = &Variable[_tcslen(Variable)];
+ if (*Variable == _T('"'))
{
- case _T('~'):
- varName++;
- if (_tcsncicmp(varName, _T("dp0"), 3) == 0)
+ Variable++;
+ if (VarEnd > Variable && VarEnd[-1] == _T('"'))
+ VarEnd--;
+ }
+
+ if ((char *)VarEnd - (char *)Variable >= sizeof Result)
+ return _T("");
+ memcpy(Result, Variable, (char *)VarEnd - (char *)Variable);
+ Result[VarEnd - Variable] = _T('\0');
+
+ if (PathVarName)
+ {
+ /* $PATH: syntax - search the directories listed in the
+ * specified environment variable for the file */
+ LPTSTR PathVar;
+ FormatEnd[-1] = _T('\0');
+ PathVar = GetEnvVar(PathVarName);
+ FormatEnd[-1] = _T(':');
+ if (!PathVar ||
+ !SearchPath(PathVar, Result, NULL, MAX_PATH, FullPath, NULL))
{
- *varNameLen = 4;
- len = _tcsrchr(bc->BatchFilePath, _T('\\')) + 1 - bc->BatchFilePath;
- if (!GrowIfNecessary(len + 1, &ret, &retlen))
- return NULL;
- memcpy(ret, bc->BatchFilePath, len * sizeof(TCHAR));
- ret[len] = _T('\0');
- return ret;
+ return _T("");
}
+ }
+ else if (Modifiers == 0)
+ {
+ /* For plain %~var with no modifiers, just return the variable without quotes */
+ return Result;
+ }
+ else if (VariableIsParam0)
+ {
+ /* Special case: If the variable is %0 and modifier characters are present,
+ * use the batch file's path (which includes the .bat/.cmd extension)
+ * rather than the actual %0 variable (which might not). */
+ _tcscpy(FullPath, bc->BatchFilePath);
+ }
+ else
+ {
+ /* Convert the variable, now without quotes, to a full path */
+ if (!GetFullPathName(Result, MAX_PATH, FullPath, NULL))
+ return _T("");
+ }
- *varNameLen = 2;
- if (*varName >= _T('0') && *varName <= _T('9')) {
- LPTSTR arg = FindArg(*varName - _T('0'));
+ /* Next step is to change the path to fix letter case (e.g.
+ * C:\ReAcToS -> C:\ReactOS) and, if requested with the S modifier,
+ * replace long filenames with short. */
+
+ In = FullPath;
+ Out = FixedPath;
+
+ /* Copy drive letter */
+ *Out++ = *In++;
+ *Out++ = *In++;
+ *Out++ = *In++;
+ /* Loop over each \-separated component in the path */
+ do {
+ TCHAR *Next = _tcschr(In, _T('\\'));
+ if (Next)
+ *Next++ = _T('\0');
+ /* Use FindFirstFile to get the correct name */
+ if (Out + _tcslen(In) + 1 >= &FixedPath[MAX_PATH])
+ return _T("");
+ _tcscpy(Out, In);
+ hFind = FindFirstFile(FixedPath, &w32fd);
+ /* If it doesn't exist, just leave the name as it was given */
+ if (hFind != INVALID_HANDLE_VALUE)
+ {
+ LPTSTR FixedComponent = w32fd.cFileName;
+ if (*w32fd.cAlternateFileName &&
+ ((Modifiers & M_SHORT) || !_tcsicmp(In, w32fd.cAlternateFileName)))
+ {
+ FixedComponent = w32fd.cAlternateFileName;
+ }
+ FindClose(hFind);
- if (*arg != _T('"'))
- return arg;
+ if (Out + _tcslen(FixedComponent) + 1 >= &FixedPath[MAX_PATH])
+ return _T("");
+ _tcscpy(Out, FixedComponent);
+ }
+ Filename = Out;
+ Out += _tcslen(Out);
+ *Out++ = _T('\\');
+
+ In = Next;
+ } while (In != NULL);
+ Out[-1] = _T('\0');
+
+ /* Build the result string. Start with attributes, modification time, and
+ * file size. If the file didn't exist, these fields will all be empty. */
+ Out = Result;
+ if (hFind != INVALID_HANDLE_VALUE)
+ {
+ if (Modifiers & M_ATTR)
+ {
+ static const struct {
+ TCHAR Character;
+ WORD Value;
+ } *Attrib, Table[] = {
+ { _T('d'), FILE_ATTRIBUTE_DIRECTORY },
+ { _T('r'), FILE_ATTRIBUTE_READONLY },
+ { _T('a'), FILE_ATTRIBUTE_ARCHIVE },
+ { _T('h'), FILE_ATTRIBUTE_HIDDEN },
+ { _T('s'), FILE_ATTRIBUTE_SYSTEM },
+ { _T('c'), FILE_ATTRIBUTE_COMPRESSED },
+ { _T('o'), FILE_ATTRIBUTE_OFFLINE },
+ { _T('t'), FILE_ATTRIBUTE_TEMPORARY },
+ { _T('l'), FILE_ATTRIBUTE_REPARSE_POINT },
+ };
+ for (Attrib = Table; Attrib != &Table[9]; Attrib++)
+ {
+ *Out++ = w32fd.dwFileAttributes & Attrib->Value
+ ? Attrib->Character
+ : _T('-');
+ }
+ *Out++ = _T(' ');
+ }
+ if (Modifiers & M_TIME)
+ {
+ FILETIME ft;
+ SYSTEMTIME st;
+ FileTimeToLocalFileTime(&w32fd.ftLastWriteTime, &ft);
+ FileTimeToSystemTime(&ft, &st);
+
+ Out += FormatDate(Out, &st, TRUE);
+ *Out++ = _T(' ');
+ Out += FormatTime(Out, &st);
+ *Out++ = _T(' ');
+ }
+ if (Modifiers & M_SIZE)
+ {
+ ULARGE_INTEGER Size;
+ Size.LowPart = w32fd.nFileSizeLow;
+ Size.HighPart = w32fd.nFileSizeHigh;
+ Out += _stprintf(Out, _T("%I64u "), Size.QuadPart);
+ }
+ }
+
+ /* When using the path-searching syntax or the S modifier,
+ * at least part of the file path is always included.
+ * If none of the DPNX modifiers are present, include the full path */
+ if (PathVarName || (Modifiers & M_SHORT))
+ if ((Modifiers & (M_DRIVE | M_PATH | M_NAME | M_EXT)) == 0)
+ Modifiers |= M_FULL;
+
+ /* Now add the requested parts of the name.
+ * With the F modifier, add all parts to form the full path. */
+ Extension = _tcsrchr(Filename, _T('.'));
+ if (Modifiers & (M_DRIVE | M_FULL))
+ {
+ *Out++ = FixedPath[0];
+ *Out++ = FixedPath[1];
+ }
+ if (Modifiers & (M_PATH | M_FULL))
+ {
+ memcpy(Out, &FixedPath[2], (char *)Filename - (char *)&FixedPath[2]);
+ Out += Filename - &FixedPath[2];
+ }
+ if (Modifiers & (M_NAME | M_FULL))
+ {
+ while (*Filename && Filename != Extension)
+ *Out++ = *Filename++;
+ }
+ if (Modifiers & (M_EXT | M_FULL))
+ {
+ if (Extension)
+ Out = _stpcpy(Out, Extension);
+ }
- /* Exclude the leading and trailing quotes */
- arg++;
- len = _tcslen(arg);
- if (arg[len - 1] == _T('"'))
- len--;
+ /* Trim trailing space which otherwise would appear as a
+ * result of using the A/T/Z modifiers but no others. */
+ while (Out != &Result[0] && Out[-1] == _T(' '))
+ Out--;
+ *Out = _T('\0');
- if (!GrowIfNecessary(len + 1, &ret, &retlen))
- return NULL;
- memcpy(ret, arg, len * sizeof(TCHAR));
- ret[len] = _T('\0');
- return ret;
+ return Result;
+}
+
+LPCTSTR
+GetBatchVar(TCHAR *varName, UINT *varNameLen)
+{
+ LPCTSTR ret;
+ TCHAR *varNameEnd;
+ BOOL dummy;
+
+ *varNameLen = 1;
+
+ switch ( *varName )
+ {
+ case _T('~'):
+ varNameEnd = varName + 1;
+ ret = GetEnhancedVar(&varNameEnd, FindArg);
+ if (!ret)
+ {
+ error_syntax(varName);
+ return NULL;
}
- break;
+ *varNameLen = varNameEnd - varName;
+ return ret;
case _T('0'):
case _T('1'):
case _T('2'):
case _T('7'):
case _T('8'):
case _T('9'):
- return FindArg(*varName - _T('0'));
+ return FindArg(*varName, &dummy);
case _T('*'):
//
}
BOOL
-SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim, BOOL bIsBatch)
+SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim)
{
#define APPEND(From, Length) { \
if (Dest + (Length) > DestEnd) \
}
Src++;
- if (bIsBatch && Delim == _T('%'))
+ if (bc && Delim == _T('%'))
{
UINT NameLen;
Var = GetBatchVar(Src, &NameLen);
if (Var == NULL)
{
/* In a batch file, %NONEXISTENT% "expands" to an empty string */
- if (bIsBatch)
+ if (bc)
continue;
goto bad_subst;
}
bad_subst:
Src = SubstStart;
- if (!bIsBatch)
+ if (!bc)
APPEND1(Delim)
}
*Dest = _T('\0');
#undef APPEND1
}
+/* Search the list of FOR contexts for a variable */
+static LPTSTR FindForVar(TCHAR Var, BOOL *IsParam0)
+{
+ FOR_CONTEXT *Ctx;
+ *IsParam0 = FALSE;
+ for (Ctx = fc; Ctx != NULL; Ctx = Ctx->prev)
+ if ((UINT)(Var - Ctx->firstvar) < Ctx->varcount)
+ return Ctx->values[Var - Ctx->firstvar];
+ return NULL;
+}
+
+BOOL
+SubstituteForVars(TCHAR *Src, TCHAR *Dest)
+{
+ TCHAR *DestEnd = &Dest[CMDLINE_LENGTH - 1];
+ while (*Src)
+ {
+ if (Src[0] == _T('%'))
+ {
+ BOOL Dummy;
+ LPTSTR End = &Src[2];
+ LPTSTR Value = NULL;
+
+ if (Src[1] == _T('~'))
+ Value = GetEnhancedVar(&End, FindForVar);
+
+ if (!Value)
+ Value = FindForVar(Src[1], &Dummy);
+
+ if (Value)
+ {
+ if (Dest + _tcslen(Value) > DestEnd)
+ return FALSE;
+ Dest = _stpcpy(Dest, Value);
+ Src = End;
+ continue;
+ }
+ }
+ /* Not a variable; just copy the character */
+ if (Dest >= DestEnd)
+ return FALSE;
+ *Dest++ = *Src++;
+ }
+ *Dest = _T('\0');
+ return TRUE;
+}
+
+LPTSTR
+DoDelayedExpansion(LPTSTR Line)
+{
+ TCHAR Buf1[CMDLINE_LENGTH];
+ TCHAR Buf2[CMDLINE_LENGTH];
+
+ /* First, substitute FOR variables */
+ if (!SubstituteForVars(Line, Buf1))
+ return NULL;
+
+ if (!bDelayedExpansion || !_tcschr(Buf1, _T('!')))
+ return cmd_dup(Buf1);
+
+ /* FIXME: Delayed substitutions actually aren't quite the same as
+ * immediate substitutions. In particular, it's possible to escape
+ * the exclamation point using ^. */
+ if (!SubstituteVars(Buf1, Buf2, _T('!')))
+ return NULL;
+ return cmd_dup(Buf2);
+}
+
/*
* do the prompt/input/process loop
*
*/
-BOOL bNoInteractive;
-BOOL bIsBatch;
-
BOOL
ReadLine (TCHAR *commandline, BOOL bMore)
{
LPTSTR ip;
/* if no batch input then... */
- if (!(ip = ReadBatchLine()))
+ if (bc == NULL)
{
- if (bNoInteractive)
- {
- bExit = TRUE;
- return FALSE;
- }
-
if (bMore)
{
- ConOutPrintf(_T("More? "));
+ ConOutResPrintf(STRING_MORE);
}
else
{
/* JPP 19980807 - if echo off, don't print prompt */
if (bEcho)
+ {
+ if (!bIgnoreEcho)
+ ConOutChar('\n');
PrintPrompt();
+ }
+ }
+
+ if (!ReadCommand(readline, CMDLINE_LENGTH - 1))
+ {
+ bExit = TRUE;
+ return FALSE;
}
- ReadCommand (readline, CMDLINE_LENGTH - 1);
if (CheckCtrlBreak(BREAK_INPUT))
{
ConOutPuts(_T("\n"));
return FALSE;
}
ip = readline;
- bIsBatch = FALSE;
}
else
{
- bIsBatch = TRUE;
+ ip = ReadBatchLine();
+ if (!ip)
+ return FALSE;
}
- if (!SubstituteVars(ip, commandline, _T('%'), bIsBatch))
- return FALSE;
-
- /* FIXME: !vars! should be substituted later, after parsing. */
- if (!SubstituteVars(commandline, readline, _T('!'), bIsBatch))
- return FALSE;
- _tcscpy(commandline, readline);
-
- return TRUE;
+ return SubstituteVars(ip, commandline, _T('%'));
}
-static INT
-ProcessInput (BOOL bFlag)
+static VOID
+ProcessInput()
{
PARSED_COMMAND *Cmd;
- bNoInteractive = bFlag;
- do
+ while (!bCanExit || !bExit)
{
Cmd = ParseCommand(NULL);
if (!Cmd)
continue;
- /* JPP 19980807 */
- /* Echo batch file line */
- if (bIsBatch && bEcho && Cmd->Type != C_QUIET)
- {
- PrintPrompt ();
- EchoCommand(Cmd);
- ConOutChar(_T('\n'));
- }
-
ExecuteCommand(Cmd);
- if (bEcho && !bIgnoreEcho && (!bIsBatch || Cmd->Type != C_QUIET))
- ConOutChar ('\n');
FreeCommand(Cmd);
- bIgnoreEcho = FALSE;
}
- while (!bCanExit || !bExit);
-
- return nErrorLevel;
}
}
}
- if (bChildProcessRunning == TRUE)
+ if (!TryEnterCriticalSection(&ChildProcessRunningLock))
{
SelfGenerated = TRUE;
GenerateConsoleCtrlEvent (dwCtrlType, 0);
return TRUE;
}
-
+ else
+ {
+ LeaveCriticalSection(&ChildProcessRunningLock);
+ }
rec.EventType = KEY_EVENT;
rec.Event.KeyEvent.bKeyDown = TRUE;
#endif
static VOID
-ExecuteAutoRunFile (VOID)
+ExecuteAutoRunFile(HKEY hkeyRoot)
{
- TCHAR autorun[MAX_PATH];
- DWORD len = MAX_PATH;
+ TCHAR autorun[2048];
+ DWORD len = sizeof autorun;
HKEY hkey;
- if( RegOpenKeyEx(HKEY_LOCAL_MACHINE,
+ if (RegOpenKeyEx(hkeyRoot,
_T("SOFTWARE\\Microsoft\\Command Processor"),
0,
KEY_READ,
(LPBYTE)autorun,
&len) == ERROR_SUCCESS)
{
- ParseCommandLine (autorun);
+ if (*autorun)
+ ParseCommandLine(autorun);
}
+ RegCloseKey(hkey);
}
+}
+
+/* Get the command that comes after a /C or /K switch */
+static VOID
+GetCmdLineCommand(TCHAR *commandline, TCHAR *ptr, BOOL AlwaysStrip)
+{
+ TCHAR *LastQuote;
+
+ while (_istspace(*ptr))
+ ptr++;
+
+ /* Remove leading quote, find final quote */
+ if (*ptr == _T('"') &&
+ (LastQuote = _tcsrchr(++ptr, _T('"'))) != NULL)
+ {
+ TCHAR *Space;
+ /* Under certain circumstances, all quotes are preserved.
+ * CMD /? documents these conditions as follows:
+ * 1. No /S switch
+ * 2. Exactly two quotes
+ * 3. No "special characters" between the quotes
+ * (CMD /? says &<>()@^| but parentheses did not
+ * trigger this rule when I tested them.)
+ * 4. Whitespace exists between the quotes
+ * 5. Enclosed string is an executable filename
+ */
+ *LastQuote = _T('\0');
+ for (Space = ptr + 1; Space < LastQuote; Space++)
+ {
+ if (_istspace(*Space)) /* Rule 4 */
+ {
+ if (!AlwaysStrip && /* Rule 1 */
+ !_tcspbrk(ptr, _T("\"&<>@^|")) && /* Rules 2, 3 */
+ SearchForExecutable(ptr, commandline)) /* Rule 5 */
+ {
+ /* All conditions met: preserve both the quotes */
+ *LastQuote = _T('"');
+ _tcscpy(commandline, ptr - 1);
+ return;
+ }
+ break;
+ }
+ }
+
+ /* The conditions were not met: remove both the
+ * leading quote and the last quote */
+ _tcscpy(commandline, ptr);
+ _tcscpy(&commandline[LastQuote - ptr], LastQuote + 1);
+ return;
+ }
- RegCloseKey(hkey);
+ /* No quotes; just copy */
+ _tcscpy(commandline, ptr);
}
/*
* set up global initializations and process parameters
- *
- * argc - number of parameters to command.com
- * argv - command-line parameters
- *
*/
static VOID
-Initialize (int argc, const TCHAR* argv[])
+Initialize()
{
+ HMODULE NtDllModule;
TCHAR commandline[CMDLINE_LENGTH];
TCHAR ModuleName[_MAX_PATH + 1];
- INT i;
TCHAR lpBuffer[2];
+ INT nExitCode;
//INT len;
- //TCHAR *ptr, *cmdLine;
+ TCHAR *ptr, *cmdLine, option = 0;
+ BOOL AlwaysStrip = FALSE;
+ BOOL AutoRun = TRUE;
/* get version information */
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
/* Some people like to run ReactOS cmd.exe on Win98, it helps in the
* build process. So don't link implicitly against ntdll.dll, load it
* dynamically instead */
-
- if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT)
- {
- /* ntdll is always present on NT */
- NtDllModule = GetModuleHandle(TEXT("ntdll.dll"));
- }
- else
- {
- /* not all 9x versions have a ntdll.dll, try to load it */
- NtDllModule = LoadLibrary(TEXT("ntdll.dll"));
- }
-
+ NtDllModule = GetModuleHandle(TEXT("ntdll.dll"));
if (NtDllModule != NULL)
{
NtQueryInformationProcessPtr = (NtQueryInformationProcessProc)GetProcAddress(NtDllModule, "NtQueryInformationProcess");
NtReadVirtualMemoryPtr = (NtReadVirtualMemoryProc)GetProcAddress(NtDllModule, "NtReadVirtualMemory");
}
-
- TRACE ("[command args:\n");
- for (i = 0; i < argc; i++)
- {
- TRACE ("%d. %s\n", i, debugstr_aw(argv[i]));
- }
- TRACE ("]\n");
-
InitLocale ();
/* get default input and output console handles */
if (GetEnvironmentVariable(_T("PROMPT"),lpBuffer, sizeof(lpBuffer) / sizeof(lpBuffer[0])) == 0)
SetEnvironmentVariable (_T("PROMPT"), _T("$P$G"));
+#ifdef FEATURE_DIR_STACK
+ /* initialize directory stack */
+ InitDirectoryStack ();
+#endif
+
+#ifdef FEATURE_HISTORY
+ /*initialize history*/
+ InitHistory();
+#endif
- if (argc >= 2 && !_tcsncmp (argv[1], _T("/?"), 2))
+ /* Set COMSPEC environment variable */
+ if (0 != GetModuleFileName (NULL, ModuleName, _MAX_PATH + 1))
{
- ConOutResPaging(TRUE,STRING_CMD_HELP8);
- cmd_exit(0);
+ ModuleName[_MAX_PATH] = _T('\0');
+ SetEnvironmentVariable (_T("COMSPEC"), ModuleName);
}
+
+ /* add ctrl break handler */
+ AddBreakHandler ();
+
+
SetConsoleMode (hIn, ENABLE_PROCESSED_INPUT);
-#ifdef INCLUDE_CMD_CHDIR
- InitLastPath ();
-#endif
+ cmdLine = GetCommandLine();
+ TRACE ("[command args: %s]\n", debugstr_aw(cmdLine));
- if (argc >= 2)
+ for (ptr = cmdLine; *ptr; ptr++)
{
- for (i = 1; i < argc; i++)
+ if (*ptr == _T('/'))
{
- if (!_tcsicmp (argv[i], _T("/p")))
+ option = _totupper(ptr[1]);
+ if (option == _T('?'))
+ {
+ ConOutResPaging(TRUE,STRING_CMD_HELP8);
+ nErrorLevel = 1;
+ bExit = TRUE;
+ return;
+ }
+ else if (option == _T('P'))
{
if (!IsExistingFile (_T("\\autoexec.bat")))
{
}
bCanExit = FALSE;
}
- else if (!_tcsicmp (argv[i], _T("/c")))
+ else if (option == _T('A'))
{
- /* This just runs a program and exits */
- ++i;
- if (i < argc)
- {
- _tcscpy (commandline, argv[i]);
- while (++i < argc)
- {
- _tcscat (commandline, _T(" "));
- _tcscat (commandline, argv[i]);
- }
-
- ParseCommandLine(commandline);
- cmd_exit (ProcessInput (TRUE));
- }
- else
- {
- cmd_exit (0);
- }
+ bUnicodeOutput = FALSE;
}
- else if (!_tcsicmp (argv[i], _T("/k")))
+ else if (option == _T('C') || option == _T('K') || option == _T('R'))
{
- /* This just runs a program and remains */
- ++i;
- if (i < argc)
- {
- _tcscpy (commandline, _T("\""));
- _tcscat (commandline, argv[i]);
- _tcscat (commandline, _T("\""));
- while (++i < argc)
- {
- _tcscat (commandline, _T(" "));
- _tcscat (commandline, argv[i]);
- }
- ParseCommandLine(commandline);
- }
+ /* Remainder of command line is a command to be run */
+ break;
+ }
+ else if (option == _T('D'))
+ {
+ AutoRun = FALSE;
+ }
+ else if (option == _T('Q'))
+ {
+ bDisableBatchEcho = TRUE;
+ }
+ else if (option == _T('S'))
+ {
+ AlwaysStrip = TRUE;
}
#ifdef INCLUDE_CMD_COLOR
- else if (!_tcsnicmp (argv[i], _T("/t:"), 3))
+ else if (!_tcsnicmp(ptr, _T("/T:"), 3))
{
/* process /t (color) argument */
- wDefColor = (WORD)_tcstoul (&argv[i][3], NULL, 16);
- wColor = wDefColor;
- SetScreenColor (wColor, TRUE);
+ wDefColor = (WORD)_tcstoul(&ptr[3], &ptr, 16);
+ SetScreenColor(wDefColor, TRUE);
}
#endif
+ else if (option == _T('U'))
+ {
+ bUnicodeOutput = TRUE;
+ }
+ else if (option == _T('V'))
+ {
+ bDelayedExpansion = _tcsnicmp(&ptr[2], _T(":OFF"), 4);
+ }
}
}
- else
- {
- /* Display a simple version string */
- ConOutPrintf(_T("ReactOS Operating System [Version %s-%s]\n"),
- _T(KERNEL_RELEASE_STR),
- _T(KERNEL_VERSION_BUILD_STR));
- ConOutPuts (_T("(C) Copyright 1998-") _T(COPYRIGHT_YEAR) _T(" ReactOS Team.\n"));
- }
-
- ExecuteAutoRunFile ();
-
-#ifdef FEATURE_DIR_STACK
- /* initialize directory stack */
- InitDirectoryStack ();
-#endif
-
-
-#ifdef FEATURE_HISTORY
- /*initialize history*/
- InitHistory();
-#endif
+ if (!*ptr)
+ {
+ /* If neither /C or /K was given, display a simple version string */
+ ConOutResPrintf(STRING_REACTOS_VERSION,
+ _T(KERNEL_RELEASE_STR),
+ _T(KERNEL_VERSION_BUILD_STR));
+ ConOutPuts(_T("(C) Copyright 1998-") _T(COPYRIGHT_YEAR) _T(" ReactOS Team."));
+ }
- /* Set COMSPEC environment variable */
- if (0 != GetModuleFileName (NULL, ModuleName, _MAX_PATH + 1))
+ if (AutoRun)
{
- ModuleName[_MAX_PATH] = _T('\0');
- SetEnvironmentVariable (_T("COMSPEC"), ModuleName);
+ ExecuteAutoRunFile(HKEY_LOCAL_MACHINE);
+ ExecuteAutoRunFile(HKEY_CURRENT_USER);
}
- /* add ctrl break handler */
- AddBreakHandler ();
+ if (*ptr)
+ {
+ /* Do the /C or /K command */
+ GetCmdLineCommand(commandline, &ptr[2], AlwaysStrip);
+ nExitCode = ParseCommandLine(commandline);
+ if (option != _T('K'))
+ {
+ nErrorLevel = nExitCode;
+ bExit = TRUE;
+ }
+ }
}
-static VOID Cleanup (int argc, const TCHAR *argv[])
+static VOID Cleanup()
{
/* run cmdexit.bat */
if (IsExistingFile (_T("cmdexit.bat")))
DestroyDirectoryStack ();
#endif
-#ifdef INCLUDE_CMD_CHDIR
- FreeLastPath ();
-#endif
-
#ifdef FEATURE_HISTORY
CleanHistory();
#endif
+ /* free GetEnvVar's buffer */
+ GetEnvVar(NULL);
/* remove ctrl break handler */
RemoveBreakHandler ();
SetConsoleMode( GetStdHandle( STD_INPUT_HANDLE ),
ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT );
-
- if (NtDllModule != NULL)
- {
- FreeLibrary(NtDllModule);
- }
+ DeleteCriticalSection(&ChildProcessRunningLock);
}
/*
*/
int cmd_main (int argc, const TCHAR *argv[])
{
+ HANDLE hConsole;
TCHAR startPath[MAX_PATH];
CONSOLE_SCREEN_BUFFER_INFO Info;
- INT nExitCode;
+
+ InitializeCriticalSection(&ChildProcessRunningLock);
+ lpOriginalEnvironment = DuplicateEnvironment();
GetCurrentDirectory(MAX_PATH,startPath);
_tchdir(startPath);
hConsole = CreateFile(_T("CONOUT$"), GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, 0, NULL);
- if (GetConsoleScreenBufferInfo(hConsole, &Info) == FALSE)
+ if (hConsole != INVALID_HANDLE_VALUE)
{
- ConErrFormatMessage(GetLastError());
- return(1);
+ if (!GetConsoleScreenBufferInfo(hConsole, &Info))
+ {
+ ConErrFormatMessage(GetLastError());
+ return(1);
+ }
+ wDefColor = Info.wAttributes;
+ CloseHandle(hConsole);
}
- wColor = Info.wAttributes;
- wDefColor = wColor;
InputCodePage= GetConsoleCP();
OutputCodePage = GetConsoleOutputCP();
CMD_ModuleHandle = GetModuleHandle(NULL);
/* check switches on command-line */
- Initialize(argc, argv);
+ Initialize();
/* call prompt routine */
- nExitCode = ProcessInput(FALSE);
+ ProcessInput();
/* do the cleanup */
- Cleanup(argc, argv);
+ Cleanup();
+
+ cmd_free(lpOriginalEnvironment);
- cmd_exit(nExitCode);
- return(nExitCode);
+ cmd_exit(nErrorLevel);
+ return(nErrorLevel);
}
/* EOF */