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

Miguel de Icaza miguel at novell.com
Wed Jun 6 11:14:16 EDT 2007


Hello,

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

This is already a dependency anyways (CodeDOM and anything that uses
CodeDOM for example).

> 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.)

Thanks, I'll investigate, but I rather have it in unmanaged land as it
will likely create less managed objects.

> 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.

This one can go both ways, what if you really want to use .NET?

Introducing (a new one, in addition to the current one) environment
variable is not my idea of the code I want to maintain.

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

Thanks!

> 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) {
> _______________________________________________
> Mono-devel-list mailing list
> Mono-devel-list at lists.ximian.com
> http://lists.ximian.com/mailman/listinfo/mono-devel-list



More information about the Mono-devel-list mailing list