In a pipeline ("prog1 | prog2") run all programs simultaneously, using a real pipe instead of a temporary file. Output from RosBE "make" is now visible immediately instead of having to wait for it to complete.

svn path=/trunk/; revision=39991
This commit is contained in:
Jeffrey Morlan 2009-03-12 22:04:59 +00:00
parent f7a722ebda
commit 8c83602d51
3 changed files with 204 additions and 145 deletions

View file

@ -695,172 +695,130 @@ VOID ParseCommandLine (LPTSTR 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)
{
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)
{
error_out_of_memory();
return NULL;
}
_tcscpy(ParamsEnd, _T("\""));
memset(&stui, 0, sizeof stui);
stui.cb = sizeof(STARTUPINFO);
if (!CreateProcess(CmdPath, CmdParams, NULL, NULL, TRUE, 0,
NULL, NULL, &stui, &prci))
{
ErrorMessage(GetLastError(), NULL);
return NULL;
}
CloseHandle(prci.hThread);
return prci.hProcess;
}
static VOID
ExecutePipeline(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 */
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;
//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)
/* Do all but the last pipe command */
do
{
Attributes = GetFileAttributes(szTempPath);
if (Attributes == 0xffffffff ||
!(Attributes & FILE_ATTRIBUTE_DIRECTORY))
HANDLE hPipeRead, hPipeWrite;
if (nProcesses > (MAXIMUM_WAIT_OBJECTS - 2))
{
Length = 0;
}
}
if (Length == 0 || Length >= MAX_PATH)
{
_tcscpy(szTempPath, _T(".\\"));
}
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;
while (Cmd->Type == C_PIPE)
{
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)
{
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);
hProcess[nProcesses] = ExecuteAsync(Cmd);
if (!hProcess[nProcesses])
goto failed;
nProcesses++;
CloseHandle(hInput);
SetStdHandle(STD_INPUT_HANDLE, hOldConIn);
/* Wait for all processes to complete */
bChildProcessRunning = TRUE;
WaitForMultipleObjects(nProcesses, hProcess, TRUE, INFINITE);
bChildProcessRunning = FALSE;
/* 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)
{
TerminateProcess(hProcess[nProcesses], 0);
CloseHandle(hProcess[nProcesses]);
}
SetStdHandle(STD_INPUT_HANDLE, hOldConIn);
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))
{
/* delete old stdin file, if it is a real file */
CloseHandle (hFile[0]);
hFile[0] = INVALID_HANDLE_VALUE;
DeleteFile (szFileName[0]);
*szFileName[0] = _T('\0');
}
/* Restore original STDIN */
if (hOldConIn != INVALID_HANDLE_VALUE)
{
HANDLE hIn = GetStdHandle (STD_INPUT_HANDLE);
SetStdHandle (STD_INPUT_HANDLE, hOldConIn);
if (hOldConIn != hIn)
CloseHandle (hIn);
hOldConIn = INVALID_HANDLE_VALUE;
}
else
{
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 */
}
BOOL

View file

@ -384,6 +384,7 @@ typedef struct _PARSED_COMMAND
} PARSED_COMMAND;
PARSED_COMMAND *ParseCommand(LPTSTR Line);
VOID EchoCommand(PARSED_COMMAND *Cmd);
TCHAR *Unparse(PARSED_COMMAND *Cmd, TCHAR *Out, TCHAR *OutEnd);
VOID FreeCommand(PARSED_COMMAND *Cmd);

View file

@ -807,6 +807,106 @@ EchoCommand(PARSED_COMMAND *Cmd)
}
}
/* "Unparse" a command into a text form suitable for passing to CMD /C.
* Used for pipes. This is basically the same thing as EchoCommand, but
* writing into a string instead of to standard output. */
TCHAR *
Unparse(PARSED_COMMAND *Cmd, TCHAR *Out, TCHAR *OutEnd)
{
TCHAR Buf[CMDLINE_LENGTH];
PARSED_COMMAND *Sub;
REDIRECTION *Redir;
/* Since this function has the annoying requirement that it must avoid
* overflowing the supplied buffer, define some helper macros to make
* this less painful */
#define CHAR(Char) { \
if (Out == OutEnd) return NULL; \
*Out++ = Char; }
#define STRING(String) { \
if (Out + _tcslen(String) > OutEnd) return NULL; \
Out = _stpcpy(Out, String); }
#define PRINTF(Format, ...) { \
UINT Len = _sntprintf(Out, OutEnd - Out, Format, __VA_ARGS__); \
if (Len > (UINT)(OutEnd - Out)) return NULL; \
Out += Len; }
#define RECURSE(Subcommand) { \
Out = Unparse(Subcommand, Out, OutEnd); \
if (!Out) return NULL; }
switch (Cmd->Type)
{
case C_COMMAND:
if (!SubstituteForVars(Cmd->Command.CommandLine, Buf)) return NULL;
/* This is fragile since there could be special characters, but
* Windows doesn't bother escaping them, so for compatibility
* we probably shouldn't do it either */
STRING(Buf)
break;
case C_QUIET:
CHAR(_T('@'))
RECURSE(Cmd->Subcommands)
break;
case C_BLOCK:
CHAR(_T('('))
for (Sub = Cmd->Subcommands; Sub; Sub = Sub->Next)
{
RECURSE(Sub)
if (Sub->Next)
CHAR(_T('&'))
}
CHAR(_T(')'))
break;
case C_MULTI:
case C_IFFAILURE:
case C_IFSUCCESS:
case C_PIPE:
Sub = Cmd->Subcommands;
RECURSE(Sub)
PRINTF(_T(" %s "), OpString[Cmd->Type - C_OP_LOWEST])
RECURSE(Sub->Next)
break;
case C_IF:
STRING(_T("if"))
if (Cmd->If.Flags & IFFLAG_IGNORECASE)
STRING(_T(" /I"))
if (Cmd->If.Flags & IFFLAG_NEGATE)
STRING(_T(" not"))
if (Cmd->If.LeftArg && SubstituteForVars(Cmd->If.LeftArg, Buf))
PRINTF(_T(" %s"), Buf)
PRINTF(_T(" %s"), IfOperatorString[Cmd->If.Operator]);
if (!SubstituteForVars(Cmd->If.RightArg, Buf)) return NULL;
PRINTF(_T(" %s "), Buf)
Sub = Cmd->Subcommands;
RECURSE(Sub)
if (Sub->Next)
{
STRING(_T(" else "))
RECURSE(Sub->Next)
}
break;
case C_FOR:
STRING(_T("for"))
if (Cmd->For.Switches & FOR_DIRS) STRING(_T(" /D"))
if (Cmd->For.Switches & FOR_F) STRING(_T(" /F"))
if (Cmd->For.Switches & FOR_LOOP) STRING(_T(" /L"))
if (Cmd->For.Switches & FOR_RECURSIVE) STRING(_T(" /R"))
if (Cmd->For.Params)
PRINTF(_T(" %s"), Cmd->For.Params)
PRINTF(_T(" %%%c in (%s) do "), Cmd->For.Variable, Cmd->For.List)
RECURSE(Cmd->Subcommands)
break;
}
for (Redir = Cmd->Redirections; Redir; Redir = Redir->Next)
{
if (!SubstituteForVars(Redir->Filename, Buf)) return NULL;
PRINTF(_T(" %c%s%s"), _T('0') + Redir->Number,
RedirString[Redir->Type], Buf)
}
return Out;
}
VOID
FreeCommand(PARSED_COMMAND *Cmd)
{