Allow CLR binaries to be passed to Process.Start
authorRobin Neatherway <robin.neatherway@gmail.com>
Mon, 2 Jun 2014 22:08:24 +0000 (23:08 +0100)
committerRobin Neatherway <robin.neatherway@gmail.com>
Sun, 14 Sep 2014 17:44:02 +0000 (18:44 +0100)
On *nix files are not executable by default where on Windows they
are. This causes problems if installing a NuGet package on *nix that
contains .exe files intended to be used by build tasks. On Windows the
build will invoke these without complaining whereas on *nix the build
will fail with "Native error: cannot find file specified".

On mono, the action that will be taken is to pass the path to the .exe
file to mono as a command-line argument. If this was done in a shell it
would succeed whether or not the .exe file has the executable bit set,
so it seems that this should also succeed within a program.

With thanks to @akoeplinger for help designing the test.

I release this under the MIT license.

mono/io-layer/processes.c
mono/tests/Makefile.am
mono/tests/bug-17537-helper.cs [new file with mode: 0644]
mono/tests/bug-17537.cs [new file with mode: 0644]

index c65bf23813c0ce954166d7595113fdf40ca6a3e7..590233e0b695d038655d45ca937a67b5dd407f28 100644 (file)
@@ -521,6 +521,19 @@ gboolean CreateProcessWithLogonW (const gunichar2 *username,
        return CreateProcess (appname, cmdline, NULL, NULL, FALSE, create_flags, env, cwd, startup, process_info);
 }
 
+static gboolean
+is_readable (const char *prog)
+{
+       struct stat buf;
+       if (access (prog, R_OK) != 0)
+               return FALSE;
+       if (stat (prog, &buf))
+               return FALSE;
+       if (S_ISREG (buf.st_mode))
+               return TRUE;
+       return FALSE;
+}
+
 static gboolean
 is_executable (const char *prog)
 {
@@ -655,7 +668,7 @@ gboolean CreateProcess (const gunichar2 *appname, const gunichar2 *cmdline,
                        prog = g_strdup (unquoted);
 
                        /* Executable existing ? */
-                       if (!is_executable (prog)) {
+                       if (!is_readable (prog)) {
                                DEBUG ("%s: Couldn't find executable %s",
                                           __func__, prog);
                                g_free (unquoted);
@@ -671,8 +684,8 @@ gboolean CreateProcess (const gunichar2 *appname, const gunichar2 *cmdline,
                        prog = g_strdup_printf ("%s/%s", curdir, unquoted);
                        g_free (curdir);
 
-                       /* And make sure it's executable */
-                       if (!is_executable (prog)) {
+                       /* And make sure it's readable */
+                       if (!is_readable (prog)) {
                                DEBUG ("%s: Couldn't find executable %s",
                                           __func__, prog);
                                g_free (unquoted);
@@ -763,7 +776,7 @@ gboolean CreateProcess (const gunichar2 *appname, const gunichar2 *cmdline,
                        prog = g_strdup (token);
                        
                        /* Executable existing ? */
-                       if (!is_executable (prog)) {
+                       if (!is_readable (prog)) {
                                DEBUG ("%s: Couldn't find executable %s",
                                           __func__, token);
                                g_free (token);
@@ -785,8 +798,10 @@ gboolean CreateProcess (const gunichar2 *appname, const gunichar2 *cmdline,
 
                        /* I assume X_OK is the criterion to use,
                         * rather than F_OK
+                        *
+                        * X_OK is too strict *if* the target is a CLR binary
                         */
-                       if (!is_executable (prog)) {
+                       if (!is_readable (prog)) {
                                g_free (prog);
                                prog = g_find_program_in_path (token);
                                if (prog == NULL) {
@@ -843,6 +858,13 @@ gboolean CreateProcess (const gunichar2 *appname, const gunichar2 *cmdline,
                                goto free_strings;
                        }
                }
+       } else {
+               if (!is_executable (prog)) {
+                       DEBUG ("%s: Executable permisson not set on %s", __func__, prog);
+                       g_free (prog);
+                       SetLastError (ERROR_ACCESS_DENIED);
+                       goto free_strings;
+               }
        }
 
        if (args_after_prog != NULL && *args_after_prog) {
index 7d448d374ad7308e8e7073ac6f7fd181e2f64e58..38d490666661161a5eb2c2c3f050c5c331442ec6 100644 (file)
@@ -400,7 +400,8 @@ BASE_TEST_CS_SRC=           \
        unload-appdomain-on-shutdown.cs \
        block_guard_restore_aligment_on_exit.cs \
        thread_static_gc_layout.cs \
-       sleep.cs
+       sleep.cs \
+       bug-17537.cs
 
 TEST_CS_SRC_DIST=      \
        $(BASE_TEST_CS_SRC)     \
@@ -1188,6 +1189,14 @@ bug-382986-lib.dll: bug-382986-lib.cs
 bug-382986.exe: bug-382986.cs bug-382986-lib.dll
        $(MCS) -out:$@ -r:bug-382986-lib.dll $(srcdir)/bug-382986.cs
 
+EXTRA_DIST += bug-17537-helper.cs
+bug-17537-helper.exe: bug-17537-helper.cs
+       $(MCS) -out:$@ $(srcdir)/bug-17537-helper.cs
+       chmod -x $@
+
+bug-17537.exe: bug-17537.cs bug-17537-helper.exe
+       $(MCS) -out:$@ $(srcdir)/bug-17537.cs
+
 EXTRA_DIST += coreclr-security.cs
 
 coreclr-security.exe : coreclr-security.cs
diff --git a/mono/tests/bug-17537-helper.cs b/mono/tests/bug-17537-helper.cs
new file mode 100644 (file)
index 0000000..315bb47
--- /dev/null
@@ -0,0 +1,10 @@
+using System;
+using System.IO;
+
+public class Test {
+       public static int Main(string[] args)
+       {
+               Console.WriteLine ("Hello from bug-17537-helper!");
+               return 0;
+       }
+}
diff --git a/mono/tests/bug-17537.cs b/mono/tests/bug-17537.cs
new file mode 100644 (file)
index 0000000..747ac36
--- /dev/null
@@ -0,0 +1,41 @@
+using System;
+using System.IO;
+using System.Diagnostics;
+
+public class Test {
+       public static int Main(string[] args)
+       {
+               // Only run this test on Unix
+               int pl = (int) Environment.OSVersion.Platform;
+               if ((pl != 4) && (pl != 6) && (pl != 128)) {
+                       return 0;
+               }
+
+               // Try to invoke the helper assembly
+               // Return 0 only if it is successful
+               try
+               {
+                       var name = "bug-17537-helper.exe";
+                       Console.WriteLine ("Launching subprocess: {0}", name);
+                       var p = new Process();
+                       p.StartInfo.FileName = Path.Combine (AppDomain.CurrentDomain.BaseDirectory + name);
+                       p.StartInfo.UseShellExecute = false;
+
+                       var result = p.Start();
+                       p.WaitForExit(1000);
+                       if (result) {
+                               Console.WriteLine ("Subprocess started successfully");
+                               return 0;
+                       } else {
+                               Console.WriteLine ("Subprocess failure");
+                               return 1;
+                       }
+               }
+               catch (Exception e)
+               {
+                       Console.WriteLine ("Subprocess exception");
+                       Console.WriteLine (e.Message);
+                       return 1;
+               }
+       }
+}