[Mono-dev] PATCH: Make Process.Start() use thesame'mono'runtime

Jonathan Gilbert 2a5gjx302 at sneakemail.com
Wed Jun 6 02:22:26 EDT 2007


At 12:23 AM 6/5/2007 -0400, Miguel de Icaza wrote:
>Hello,
>
>> I still prefer the environment variable approach. Are there any serious
>> disadvantages? Here are some additional thoughts:
>
>And I still think that we will use a hardcoded value for the
>interpreter.

This makes having 'mono' in $PATH an official requirement for mono to
operate correctly...

>> - If it isn't going to work to have the code inside CreateProcess, then if
>> it is implemented using an environment variable, the code for detecting CLR
>> binaries can be 100% managed. The only downside is that the logic for
>> determining the full path to the binary then needs to be duplicated, but it
>> needs to be duplicated anyway if the implementation can't be inside
>> CreateProcess, and if this is done in managed code, it can take advantage
>> of things like Path.GetFullName().
>
>I think we can keep the code inside CreateProcess,

As Robert Jordan pointed out, this means it will have no effect on Windows.
For this reason, and also because I prefer managed code, I've rewritten the
support into Process.Create (attached). You can choose which patch you wish
to apply :-) (I couldn't resist answering a question in a LAMESPEC comment
and correcting a minor error in another comment; you may split off or omit
those parts of the patch if you think they are inappropriately mixing
changes.)

>> - Tools can be written to compare behaviour and/or performance of .NET
>> applications across different runtimes. For instance, on UNIX, code aware
>> of the behaviour could select between mono and DotGNU by setting the
>> environment variable. 
>
>Tools to do that can much more easily specify the runtime to use by
>calling the specific interpreter instead of depending on yet another
>obscure environment variable that if we are remotely lucky will be
>documented.
>
>Ie, Process.Start ("mono program.exe") and Process.Start ("clix
>program.exe") much easier than setting the environment and then calling
>program.exe

But it is not inherited. If a tool is running a program which could
potentially spawn a child process, that child process could potentially not
start at all, or start with the wrong runtime. I mean, I think it's a fair
assumption that if anyone is using mono to run .NET apps on Windows,
they're doing it for a reason, and for that same reason they would want
child processes to also run under mono.

>> Shall I rework the patch to do the check directly inside
>> System.Diagnostics.Process.Start?
>
>I think your current patch is fine, I only ask you for either:
>
>	* Sign a copyright assignment form, so we can get the code
>	  in the runtime.
>
>	* License the patch under the MIT X11 code, so we are allowed
>	  to relicense it.

The code in both patches is hereby officially licensed under the terms of
the MIT X11 license. :-)

Enjoy,

Jonathan Gilbert

The below is for review only; please use the attached gzip when applying
the patch to your local source tree.

Index: class/System/System.Diagnostics/Process.cs
===================================================================
--- mcs/class/System/System.Diagnostics/Process.cs	(revision 78469)
+++ mcs/class/System/System.Diagnostics/Process.cs	(working copy)
@@ -248,6 +248,11 @@
 		private extern static bool SetWorkingSet_internal(IntPtr handle, int
min, int max, bool use_min);
 
 		/* LAMESPEC: why is this an IntPtr not a plain int? */
+		/* One might speculate that on Windows, the API call
+		 * used by MS.NET takes a 32-bit integer on 32-bit
+		 * platforms and a 64-bit integer on 64-bit platforms.
+		 * An IntPtr thereby automatically marshals correctly.
+		 */
 		[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
 		[MonitoringDescription ("The maximum working set for this process.")]
 		public IntPtr MaxWorkingSet {
@@ -725,7 +730,7 @@
 		[MethodImplAttribute(MethodImplOptions.InternalCall)]
 		extern static bool Kill_internal (IntPtr handle, int signo);
 
-		/* int kill -> 1 KILL, 2 CloseMainWindow */
+		/* int signo -> 1 KILL, 2 CloseMainWindow */
 		bool Close (int signo)
 		{
 			if (process_handle == IntPtr.Zero)
@@ -869,6 +874,293 @@
 								  IntPtr stderr,
 								  ref ProcInfo proc_info);
 
+		private static string shell_quote (string str)
+		{
+			StringBuilder ret = null;
+
+			for (int i=0; i < str.Length; i++) {
+				switch (str [i])
+				{
+					case '!':
+					case '\'':
+					case '\\':
+					{
+						if (ret == null) {
+							ret = new StringBuilder ("'");
+							ret.Append (str, 0, i);
+						}
+
+						ret.Append ('\\').Append (str, i, 1);
+
+						break;
+					}
+					default:
+					{
+						if (ret != null) {
+							ret.Append (str, i, 1);
+						}
+
+						break;
+					}
+				}
+			}
+
+			if (ret != null)
+				return ret.Append ('\'').ToString ();
+			else
+				return str;
+		}
+
+		private static string unshellquote_extract_first_word (ref string args)
+		{
+			StringBuilder ret = new StringBuilder ();
+
+			int idx = 0;
+			while (idx < args.Length) {
+				// If we've found the first whitespace dividing off the first word
+				// from the rest of the string, stop here, and update 'args' to
+				// match what we've pulled off so far.
+				if (char.IsWhiteSpace (args, idx)) {
+					args = args.Substring (idx + 1);
+					idx = 0;
+					break;
+				}
+
+				switch (args [idx])
+				{
+					case '\'':
+					case '"':
+					{
+						char end_char = args [idx];
+
+						idx++;
+
+						while ((idx < args.Length)
+						    && (args[idx] != end_char)) {
+							if (args[idx] == '\\') {
+								idx++;
+								if (idx >= args.Length) {
+									break;
+								}
+							}
+
+							ret.Append (args, idx, 1);
+
+							idx++;
+						}
+
+						idx++;
+
+						break;
+					}
+					case '`':
+					{
+						// If the user is asking the shell to insert the output
+						// of another command to find out what this command is
+						// going to be, we give up. Sorry :-)
+						return null;
+					}
+					default:
+					{
+						if (args[idx] == '\\') {
+							idx++;
+							if (idx >= args.Length) {
+								break;
+							}
+						}
+
+						ret.Append (args, idx, 1);
+						idx++;
+
+						break;
+					}
+				}
+			}
+
+			// If we hit the end of the string before finding a word boundary,
+			// then there are no arguments after the first word.
+			if (idx >= args.Length)
+				args = null;
+
+			return ret.ToString();
+		}
+
+		private static bool is_managed_binary (string filename)
+		{
+			try
+			{
+#if NET_2_0
+				using (FileStream stream = new FileStream (filename, FileMode.Open,
FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
+#else
+				using (FileStream stream = new FileStream (filename, FileMode.Open,
FileAccess.Read, FileShare.ReadWrite))
+#endif
+				{
+					// We know we need to read a header field at offset 60.
+					if (stream.Length < 64)
+						return false;
+
+					byte[] buffer = new byte[8];
+					int num_read;
+
+					// Verify the MZ executable signature word.
+					num_read = stream.Read (buffer, 0, 2);
+
+					if ((num_read != 2)
+					 || (buffer[0] != (byte)'M')
+					 || (buffer[1] != (byte)'Z'))
+						return false;
+
+					// Get the offset of the PE header.
+					stream.Position = 60;
+
+					num_read = stream.Read (buffer, 0, 4);
+
+					if (num_read != 4)
+						return false;
+
+					int pe_header_offset = BitConverter.ToInt32 (buffer, 0);
+
+					if (pe_header_offset + 24 > stream.Length)
+						return false;
+
+					// Verify that the header we've found is in fact the PE header.
+					stream.Position = pe_header_offset;
+
+					num_read = stream.Read (buffer, 0, 4);
+
+					if ((num_read != 4)
+					 || (buffer[0] != (byte)'P')
+					 || (buffer[1] != (byte)'E')
+					 || (buffer[2] != 0)
+					 || (buffer[3] != 0))
+						return false;
+
+					// Verify that the header we want in the optional header data
+					// is present in this binary.
+					stream.Position = pe_header_offset + 20;
+
+					num_read = stream.Read (buffer, 0, 2);
+
+					if ((num_read != 2)
+					 || (BitConverter.ToInt16 (buffer, 0) < 216))
+						return false;
+
+					// Read the CLR header address and size fields. These will be
+					// zero if the binary is not managed.
+					int optional_header_offset = pe_header_offset + 24;
+
+					stream.Position = optional_header_offset + 208;
+
+					num_read = stream.Read (buffer, 0, 8);
+
+					// We are not concerned with endianness, only with
+					// whether it is zero or not.
+					int first_word = BitConverter.ToInt32 (buffer, 0);
+					int second_word = BitConverter.ToInt32 (buffer, 4);
+
+					if ((num_read != 8)
+					 || (first_word == 0)
+					 || (second_word == 0))
+						return false;
+
+					// If we get here without cacking, then in all likelihood
+					// we're looking at a CLR binary!
+					return true;
+				}
+			}
+			catch
+			{
+				// If anything at all goes wrong, then we cannot say that
+				// it is a managed binary.
+				return false;
+			}
+		}
+
+		private static ProcessStartInfo redirect_for_managed_binary
(ProcessStartInfo start_info)
+		{
+			// Check if we have a MONOEXECUTABLE environment variable. If
+			// we don't, then there is no redirection to be done.
+			string mono_executable =
Environment.GetEnvironmentVariable("MONOEXECUTABLE");
+
+			if (mono_executable == null)
+				return start_info;
+
+			// Determine the target executable and the arguments being passed to it.
+			string application_filename = null, args = null;
+
+			if (start_info.FileName != null) {
+				application_filename = start_info.FileName;
+
+				if ((start_info.Arguments != null)
+				 && (start_info.Arguments.Trim().Length > 0)) {
+					args = start_info.Arguments;
+				}
+
+				// Fall back on treating the filename as the entire command-line
+				// if it is not (only) the name of an existing file.
+				if (!File.Exists (application_filename)) {
+					if (args != null) {
+						args = application_filename + ' ' + args;
+					} else {
+						args = application_filename;
+					}
+
+					application_filename = unshellquote_extract_first_word (ref args);
+				}
+			} else if (start_info.Arguments != null) {
+				args = start_info.Arguments.TrimStart();
+
+				application_filename = unshellquote_extract_first_word (ref args);
+			}
+
+			if (application_filename == null) {
+				return start_info;
+			}
+
+			bool need_redirect = is_managed_binary (application_filename);
+
+			// Determine whether we need a redirection.
+			if (!need_redirect) {
+				return start_info;
+			}
+
+			// If we need a redirection, build a new ProcessStartInfo and
+			// then alter just the parts we need. Otherwise, just return
+			// what we were passed.
+
+			ProcessStartInfo redirected = new ProcessStartInfo ();
+
+			// Copy over all of the values.
+			redirected.CreateNoWindow = start_info.CreateNoWindow;
+			redirected.ErrorDialog = start_info.ErrorDialog;
+			redirected.ErrorDialogParentHandle = start_info.ErrorDialogParentHandle;
+			redirected.RedirectStandardError = start_info.RedirectStandardError;
+			redirected.RedirectStandardInput = start_info.RedirectStandardInput;
+			redirected.RedirectStandardOutput = start_info.RedirectStandardOutput;
+			redirected.UseShellExecute = start_info.UseShellExecute;
+			redirected.Verb = start_info.Verb;
+			redirected.WindowStyle = start_info.WindowStyle;
+			redirected.WorkingDirectory = start_info.WorkingDirectory;
+
+			if (start_info.HaveEnvVars) {
+				foreach (DictionaryEntry variable in start_info.EnvironmentVariables) {
+					redirected.EnvironmentVariables [variable.Key.ToString()] =
variable.Value.ToString ();
+				}
+			}
+
+			// Set up the redirected ProcessStartInfo to run the
+			// application binary using the value of MONOEXECUTABLE.
+			redirected.FileName = mono_executable;
+
+			if (args != null) {
+				redirected.Arguments = shell_quote(application_filename) + ' ' + args;
+			} else {
+				redirected.Arguments = shell_quote(application_filename);
+			}
+
+			return redirected;
+		}
+
 		private static bool Start_shell (ProcessStartInfo startInfo,
 						 Process process)
 		{
@@ -885,7 +1177,7 @@
 				throw new InvalidOperationException ("UseShellExecute must be false in
order to use environment variables.");
 			}
 
-			ret = ShellExecuteEx_internal (startInfo,
+			ret = ShellExecuteEx_internal (redirect_for_managed_binary (startInfo),
 						       ref proc_info);
 			if (!ret) {
 				throw new Win32Exception (-proc_info.pid);
@@ -978,7 +1270,7 @@
 				stderr_wr = MonoIO.ConsoleError;
 			}
 			
-			ret = CreateProcess_internal (startInfo,
+			ret = CreateProcess_internal (redirect_for_managed_binary (startInfo),
 						      stdin_rd, stdout_wr, stderr_wr,
 						      ref proc_info);
 			if (!ret) {

Index: mono/mono/mini/driver.c
===================================================================
--- mono/mono/mini/driver.c	(revision 78469)
+++ mono/mono/mini/driver.c	(working copy)
@@ -49,6 +49,7 @@
 #include "inssel.h"
 #include <locale.h>
 #include "version.h"
+#include <glib.h>
 
 static FILE *mini_stats_fd = NULL;
 
@@ -671,6 +672,14 @@
 	"\tDisabled:      " DISABLED_FEATURES "\n"
 	"";
 
+/*
+ * If your platform lacks setenv/unsetenv, you must upgrade your glib.
+ */
+#if !GLIB_CHECK_VERSION(2,4,0)
+#define g_setenv(a,b,c)   setenv(a,b,c)
+#define g_unsetenv(a) unsetenv(a)
+#endif
+
 int
 mono_main (int argc, char* argv[])
 {
@@ -717,6 +726,9 @@
 	g_log_set_always_fatal (G_LOG_LEVEL_ERROR);
 	g_log_set_fatal_mask (G_LOG_DOMAIN, G_LOG_LEVEL_ERROR);
 
+	if ((argv [0] != NULL) && (argv [0] [0] != 0))
+		g_setenv ("MONOEXECUTABLE", argv [0], TRUE);
+
 	opt = parse_optimizations (NULL);
 
 	for (i = 1; i < argc; ++i) {
-------------- next part --------------
A non-text attachment was scrubbed...
Name: ProcessStartFixManaged.diff.gz
Type: application/octet-stream
Size: 3511 bytes
Desc: not available
Url : http://lists.ximian.com/pipermail/mono-devel-list/attachments/20070606/9a065770/attachment.obj 
-------------- next part --------------
A non-text attachment was scrubbed...
Name: ProcessStartFixBoth.diff.gz
Type: application/octet-stream
Size: 4676 bytes
Desc: not available
Url : http://lists.ximian.com/pipermail/mono-devel-list/attachments/20070606/9a065770/attachment-0001.obj 


More information about the Mono-devel-list mailing list