[ros-diffs] [jmorlan] 39991: 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.

jmorlan at svn.reactos.org jmorlan at svn.reactos.org
Thu Mar 12 23:05:00 CET 2009


Author: jmorlan
Date: Fri Mar 13 01:04:59 2009
New Revision: 39991

URL: http://svn.reactos.org/svn/reactos?rev=39991&view=rev
Log:
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.

Modified:
    trunk/reactos/base/shell/cmd/cmd.c
    trunk/reactos/base/shell/cmd/cmd.h
    trunk/reactos/base/shell/cmd/parser.c

Modified: trunk/reactos/base/shell/cmd/cmd.c
URL: http://svn.reactos.org/svn/reactos/trunk/reactos/base/shell/cmd/cmd.c?rev=39991&r1=39990&r2=39991&view=diff
==============================================================================
--- trunk/reactos/base/shell/cmd/cmd.c [iso-8859-1] (original)
+++ trunk/reactos/base/shell/cmd/cmd.c [iso-8859-1] Fri Mar 13 01:04:59 2009
@@ -695,172 +695,130 @@
 	}
 }
 
+/* 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 */
-
-	//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)
-	{
-		Attributes = GetFileAttributes(szTempPath);
-		if (Attributes == 0xffffffff ||
-		    !(Attributes & FILE_ATTRIBUTE_DIRECTORY))
-		{
-			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;
-		}
-
-		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))
-		{
-			CloseHandle (hFile[1]);
-			hFile[1] = INVALID_HANDLE_VALUE;
-		}
-
-		/* close old stdin file */
-		SetStdHandle (STD_INPUT_HANDLE, hOldConIn);
-		if ((hFile[0] != INVALID_HANDLE_VALUE) && (hFile[0] != hOldConIn))
-		{
-			/* delete old stdin file, if it is a real file */
-			CloseHandle (hFile[0]);
-			hFile[0] = INVALID_HANDLE_VALUE;
-			DeleteFile (szFileName[0]);
-			*szFileName[0] = _T('\0');
-		}
-
-		/* copy stdout file name to stdin file name */
-		_tcscpy (szFileName[0], szFileName[1]);
-		*szFileName[1] = _T('\0');
-
-		/* we need make sure the LastError msg is zero before calling CreateFile */
-		SetLastError(0);
-
-		/* open new stdin file */
-		hFile[0] = CreateFile (szFileName[0], GENERIC_READ, 0, &sa,
-		                       OPEN_EXISTING, FILE_ATTRIBUTE_TEMPORARY, NULL);
-		SetStdHandle (STD_INPUT_HANDLE, hFile[0]);
+	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
+	{
+		HANDLE hPipeRead, hPipeWrite;
+		if (nProcesses > (MAXIMUM_WAIT_OBJECTS - 2))
+		{
+			error_too_many_parameters(_T("|"));
+			goto failed;
+		}
+
+		/* 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))
+		{
+			error_no_pipe();
+			goto failed;
+		}
+
+		/* The writing side of the pipe is STDOUT for this process */
+		SetHandleInformation(hPipeWrite, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
+		SetStdHandle(STD_OUTPUT_HANDLE, hPipeWrite);
+
+		/* Execute it (error check is done later for easier cleanup) */
+		hProcess[nProcesses] = ExecuteAsync(Cmd->Subcommands);
+		CloseHandle(hPipeWrite);
+		if (hInput)
+			CloseHandle(hInput);
+
+		/* 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;
+
+		if (!hProcess[nProcesses])
+			goto failed;
+		nProcesses++;
 
 		Cmd = Cmd->Subcommands->Next;
-	}
-
-	/* Now set up the end conditions... */
+	} while (Cmd->Type == C_PIPE);
+
+	/* 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))
-	{
-		/* 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 */
+	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
 }
 
 BOOL

Modified: trunk/reactos/base/shell/cmd/cmd.h
URL: http://svn.reactos.org/svn/reactos/trunk/reactos/base/shell/cmd/cmd.h?rev=39991&r1=39990&r2=39991&view=diff
==============================================================================
--- trunk/reactos/base/shell/cmd/cmd.h [iso-8859-1] (original)
+++ trunk/reactos/base/shell/cmd/cmd.h [iso-8859-1] Fri Mar 13 01:04:59 2009
@@ -384,6 +384,7 @@
 } 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);
 
 

Modified: trunk/reactos/base/shell/cmd/parser.c
URL: http://svn.reactos.org/svn/reactos/trunk/reactos/base/shell/cmd/parser.c?rev=39991&r1=39990&r2=39991&view=diff
==============================================================================
--- trunk/reactos/base/shell/cmd/parser.c [iso-8859-1] (original)
+++ trunk/reactos/base/shell/cmd/parser.c [iso-8859-1] Fri Mar 13 01:04:59 2009
@@ -807,6 +807,106 @@
 	}
 }
 
+/* "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)
 {



More information about the Ros-diffs mailing list