[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