Merge pull request #2003 from esdrubal/seq_test_fix2
[mono.git] / mcs / class / System / System.Diagnostics / Process.cs
index a394dc4fa78f55ec9c309f9004db034585b58d8b..a3147ffc72781bd4149b53ee4dbc5a7890e4fd3d 100644 (file)
@@ -38,10 +38,12 @@ using System.ComponentModel;
 using System.ComponentModel.Design;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
+using System.Runtime.Remoting.Messaging;
 using System.Security.Permissions;
 using System.Collections.Generic;
 using System.Security;
 using System.Threading;
+using Microsoft.Win32.SafeHandles;
 
 namespace System.Diagnostics {
 
@@ -75,12 +77,14 @@ namespace System.Diagnostics {
                IntPtr process_handle;
                int pid;
                bool enableRaisingEvents;
-               bool already_waiting;
+               RegisteredWaitHandle exitWaitHandle;
                ISynchronizeInvoke synchronizingObject;
                EventHandler exited_event;
                IntPtr stdout_rd;
                IntPtr stderr_rd;
-               
+
+               object thisLock = new Object ();
+
                /* Private constructor called from other methods */
                private Process(IntPtr handle, int id) {
                        process_handle=handle;
@@ -102,12 +106,30 @@ namespace System.Diagnostics {
 
                void StartExitCallbackIfNeeded ()
                {
-                       bool start = (!already_waiting && enableRaisingEvents && exited_event != null);
-                       if (start && process_handle != IntPtr.Zero) {
-                               WaitOrTimerCallback cb = new WaitOrTimerCallback (CBOnExit);
-                               ProcessWaitHandle h = new ProcessWaitHandle (process_handle);
-                               ThreadPool.RegisterWaitForSingleObject (h, cb, this, -1, true);
-                               already_waiting = true;
+                       lock (thisLock) {
+                               bool start = (exitWaitHandle == null && enableRaisingEvents && exited_event != null);
+                               if (start && process_handle != IntPtr.Zero) {
+                                       WaitOrTimerCallback cb = new WaitOrTimerCallback (CBOnExit);
+                                       ProcessWaitHandle h = new ProcessWaitHandle (process_handle);
+                                       exitWaitHandle = ThreadPool.RegisterWaitForSingleObject (h, cb, this, -1, true);
+                               }
+                       }
+               }
+
+               void UnregisterExitCallback ()
+               {
+                       lock (thisLock) {
+                               if (exitWaitHandle != null) {
+                                       exitWaitHandle.Unregister (null);
+                                       exitWaitHandle = null;
+                               }
+                       }
+               }
+
+               bool IsExitCallbackPending ()
+               {
+                       lock (thisLock) {
+                               return exitWaitHandle != null;
                        }
                }
 
@@ -595,7 +617,8 @@ namespace System.Diagnostics {
                }
 
                private StreamReader error_stream=null;
-               
+               bool error_stream_exposed;
+
                [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden), Browsable (false)]
                [MonitoringDescription ("The standard error stream of this process.")]
                public StreamReader StandardError {
@@ -608,11 +631,13 @@ namespace System.Diagnostics {
 
                                async_mode |= AsyncModes.SyncError;
 
+                               error_stream_exposed = true;
                                return(error_stream);
                        }
                }
 
                private StreamWriter input_stream=null;
+               bool input_stream_exposed;
                
                [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden), Browsable (false)]
                [MonitoringDescription ("The standard input stream of this process.")]
@@ -621,11 +646,13 @@ namespace System.Diagnostics {
                                if (input_stream == null)
                                        throw new InvalidOperationException("Standard input has not been redirected");
 
+                               input_stream_exposed = true;
                                return(input_stream);
                        }
                }
 
                private StreamReader output_stream=null;
+               bool output_stream_exposed;
                
                [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden), Browsable (false)]
                [MonitoringDescription ("The standard output stream of this process.")]
@@ -639,6 +666,7 @@ namespace System.Diagnostics {
 
                                async_mode |= AsyncModes.SyncOutput;
 
+                               output_stream_exposed = true;
                                return(output_stream);
                        }
                }
@@ -950,15 +978,58 @@ namespace System.Diagnostics {
                        return(ret);
                }
 
-               private static bool Start_noshell (ProcessStartInfo startInfo,
-                                                  Process process)
+               //
+               // Creates a pipe with read and write descriptors
+               //
+               static void CreatePipe (out IntPtr read, out IntPtr write, bool writeDirection)
                {
-                       ProcInfo proc_info=new ProcInfo();
-                       IntPtr stdin_rd = IntPtr.Zero, stdin_wr = IntPtr.Zero;
-                       IntPtr stdout_wr;
-                       IntPtr stderr_wr;
-                       bool ret;
-                       MonoIOError error;
+                       //
+                       // Creates read/write pipe from parent -> child perspective
+                       // a child process uses same descriptors after fork. That's
+                       // 4 descriptors in total where only 2. One in child, one in parent
+                       // should be active and the other 2 closed. Which ones depends on
+                       // comunication direction
+                       //
+                       // parent  -------->  child   (parent can write, child can read)
+                       //
+                       // read: closed       read: used
+                       // write: used        write: closed
+                       //
+                       //
+                       // parent  <--------  child   (parent can read, child can write)
+                       //
+                       // read: used         read: closed
+                       // write: closed      write: used
+                       //
+                       // It can still be tricky for predefined descriptiors http://unixwiz.net/techtips/remap-pipe-fds.html
+                       //
+                       var ret = MonoIO.CreatePipe (out read, out write);
+                       if (!ret)
+                               throw new IOException ("Error creating process pipe");
+
+                       if (IsWindows) {
+                               const int DUPLICATE_SAME_ACCESS = 0x00000002;
+                               var tmp = writeDirection ? write : read;
+
+                               ret = MonoIO.DuplicateHandle (Process.GetCurrentProcess ().Handle, tmp,
+                                       Process.GetCurrentProcess ().Handle, out tmp, 0, 0, DUPLICATE_SAME_ACCESS);
+                               if (!ret)
+                                       return;
+
+                               MonoIOError error;
+                               if (writeDirection) {
+                                       MonoIO.Close (write, out error);
+                                       write = tmp;
+                               } else {
+                                       MonoIO.Close (read, out error);
+                                       read = tmp;
+                               }
+                       }
+               }
+
+               static bool Start_noshell (ProcessStartInfo startInfo, Process process)
+               {
+                       var proc_info = new ProcInfo ();
 
                        if (startInfo.HaveEnvVars) {
                                string [] strs = new string [startInfo.EnvironmentVariables.Count];
@@ -970,164 +1041,118 @@ namespace System.Diagnostics {
                                proc_info.envValues = strs;
                        }
 
-                       if (startInfo.RedirectStandardInput == true) {
-                               if (IsWindows) {
-                                       int DUPLICATE_SAME_ACCESS = 0x00000002;
-                                       IntPtr stdin_wr_tmp;
+                       MonoIOError error;
+                       IntPtr stdin_read = IntPtr.Zero, stdin_write = IntPtr.Zero;
+                       IntPtr stdout_read = IntPtr.Zero, stdout_write = IntPtr.Zero;
+                       IntPtr stderr_read = IntPtr.Zero, stderr_write = IntPtr.Zero;
 
-                                       ret = MonoIO.CreatePipe (out stdin_rd,
-                                                                        out stdin_wr_tmp);
-                                       if (ret) {
-                                               ret = MonoIO.DuplicateHandle (Process.GetCurrentProcess ().Handle, stdin_wr_tmp,
-                                               Process.GetCurrentProcess ().Handle, out stdin_wr, 0, 0, DUPLICATE_SAME_ACCESS);
-                                               MonoIO.Close (stdin_wr_tmp, out error);
-                                       }
-                               }
-                               else
-                               {
-                                       ret = MonoIO.CreatePipe (out stdin_rd,
-                                                                        out stdin_wr);
-                               }
-                               if (ret == false) {
-                                       throw new IOException ("Error creating standard input pipe");
+                       try {
+                               if (startInfo.RedirectStandardInput) {
+                                       CreatePipe (out stdin_read, out stdin_write, true);
+                               } else {
+                                       stdin_read = MonoIO.ConsoleInput;
+                                       stdin_write = IntPtr.Zero;
                                }
-                       } else {
-                               stdin_rd = MonoIO.ConsoleInput;
-                               /* This is required to stop the
-                                * &$*£ing stupid compiler moaning
-                                * that stdin_wr is unassigned, below.
-                                */
-                               stdin_wr = (IntPtr)0;
-                       }
-
-                       if (startInfo.RedirectStandardOutput == true) {
-                               IntPtr out_rd = IntPtr.Zero;
-                               if (IsWindows) {
-                                       IntPtr out_rd_tmp;
-                                       int DUPLICATE_SAME_ACCESS = 0x00000002;
-
-                                       ret = MonoIO.CreatePipe (out out_rd_tmp,
-                                                                        out stdout_wr);
-                                       if (ret) {
-                                               MonoIO.DuplicateHandle (Process.GetCurrentProcess ().Handle, out_rd_tmp,
-                                               Process.GetCurrentProcess ().Handle, out out_rd, 0, 0, DUPLICATE_SAME_ACCESS);
-                                               MonoIO.Close (out_rd_tmp, out error);
-                                       }
+
+                               if (startInfo.RedirectStandardOutput) {
+                                       CreatePipe (out stdout_read, out stdout_write, false);
+                                       process.stdout_rd = stdout_read;
+                               } else {
+                                       process.stdout_rd = IntPtr.Zero;
+                                       stdout_write = MonoIO.ConsoleOutput;
                                }
-                               else {
-                                       ret = MonoIO.CreatePipe (out out_rd,
-                                                                        out stdout_wr);
+
+                               if (startInfo.RedirectStandardError) {
+                                       CreatePipe (out stderr_read, out stderr_write, false);
+                                       process.stderr_rd  = stderr_read;
+                               } else {
+                                       process.stderr_rd = IntPtr.Zero;
+                                       stderr_write = MonoIO.ConsoleError;
                                }
 
-                               process.stdout_rd = out_rd;
-                               if (ret == false) {
-                                       if (startInfo.RedirectStandardInput == true) {
-                                               MonoIO.Close (stdin_rd, out error);
-                                               MonoIO.Close (stdin_wr, out error);
-                                       }
+                               FillUserInfo (startInfo, ref proc_info);
 
-                                       throw new IOException ("Error creating standard output pipe");
+                               //
+                               // FIXME: For redirected pipes we need to send descriptors of
+                               // stdin_write, stdout_read, stderr_read to child process and
+                               // close them there (fork makes exact copy of parent's descriptors)
+                               //
+                               if (!CreateProcess_internal (startInfo, stdin_read, stdout_write, stderr_write, ref proc_info)) {
+                                       throw new Win32Exception (-proc_info.pid, 
+                                       "ApplicationName='" + startInfo.FileName +
+                                       "', CommandLine='" + startInfo.Arguments +
+                                       "', CurrentDirectory='" + startInfo.WorkingDirectory +
+                                       "', Native error= " + Win32Exception.W32ErrorMessage (-proc_info.pid));
                                }
-                       } else {
-                               process.stdout_rd = (IntPtr)0;
-                               stdout_wr = MonoIO.ConsoleOutput;
-                       }
-
-                       if (startInfo.RedirectStandardError == true) {
-                               IntPtr err_rd = IntPtr.Zero;
-                               if (IsWindows) {
-                                       IntPtr err_rd_tmp;
-                                       int DUPLICATE_SAME_ACCESS = 0x00000002;
-
-                                       ret = MonoIO.CreatePipe (out err_rd_tmp,
-                                                                        out stderr_wr);
-                                       if (ret) {
-                                               MonoIO.DuplicateHandle (Process.GetCurrentProcess ().Handle, err_rd_tmp,
-                                               Process.GetCurrentProcess ().Handle, out err_rd, 0, 0, DUPLICATE_SAME_ACCESS);
-                                               MonoIO.Close (err_rd_tmp, out error);
-                                       }
+                       } catch {
+                               if (startInfo.RedirectStandardInput) {
+                                       if (stdin_read != IntPtr.Zero)
+                                               MonoIO.Close (stdin_read, out error);
+                                       if (stdin_write != IntPtr.Zero)
+                                               MonoIO.Close (stdin_write, out error);
                                }
-                               else {
-                                       ret = MonoIO.CreatePipe (out err_rd,
-                                                                        out stderr_wr);
+
+                               if (startInfo.RedirectStandardOutput) {
+                                       if (stdout_read != IntPtr.Zero)
+                                               MonoIO.Close (stdout_read, out error);
+                                       if (stdout_write != IntPtr.Zero)
+                                               MonoIO.Close (stdout_write, out error);
                                }
 
-                               process.stderr_rd = err_rd;
-                               if (ret == false) {
-                                       if (startInfo.RedirectStandardInput == true) {
-                                               MonoIO.Close (stdin_rd, out error);
-                                               MonoIO.Close (stdin_wr, out error);
-                                       }
-                                       if (startInfo.RedirectStandardOutput == true) {
-                                               MonoIO.Close (process.stdout_rd, out error);
-                                               MonoIO.Close (stdout_wr, out error);
-                                       }
-                                       
-                                       throw new IOException ("Error creating standard error pipe");
+                               if (startInfo.RedirectStandardError) {
+                                       if (stderr_read != IntPtr.Zero)
+                                               MonoIO.Close (stderr_read, out error);
+                                       if (stderr_write != IntPtr.Zero)
+                                               MonoIO.Close (stderr_write, out error);
                                }
-                       } else {
-                               process.stderr_rd = (IntPtr)0;
-                               stderr_wr = MonoIO.ConsoleError;
-                       }
 
-                       FillUserInfo (startInfo, ref proc_info);
-                       try {
-                               ret = CreateProcess_internal (startInfo,
-                                                             stdin_rd, stdout_wr, stderr_wr,
-                                                             ref proc_info);
+                               throw;
                        } finally {
-                               if (proc_info.Password != IntPtr.Zero)
+                               if (proc_info.Password != IntPtr.Zero) {
                                        Marshal.ZeroFreeBSTR (proc_info.Password);
-                               proc_info.Password = IntPtr.Zero;
-                       }
-                       if (!ret) {
-                               if (startInfo.RedirectStandardInput == true) {
-                                       MonoIO.Close (stdin_rd, out error);
-                                       MonoIO.Close (stdin_wr, out error);
-                               }
-
-                               if (startInfo.RedirectStandardOutput == true) {
-                                       MonoIO.Close (process.stdout_rd, out error);
-                                       MonoIO.Close (stdout_wr, out error);
-                               }
-
-                               if (startInfo.RedirectStandardError == true) {
-                                       MonoIO.Close (process.stderr_rd, out error);
-                                       MonoIO.Close (stderr_wr, out error);
+                                       proc_info.Password = IntPtr.Zero;
                                }
-
-                               throw new Win32Exception (-proc_info.pid,
-                                       "ApplicationName='" + startInfo.FileName +
-                                       "', CommandLine='" + startInfo.Arguments +
-                                       "', CurrentDirectory='" + startInfo.WorkingDirectory +
-                                       "', Native error= " + Win32Exception.W32ErrorMessage (-proc_info.pid));
                        }
 
                        process.process_handle = proc_info.process_handle;
                        process.pid = proc_info.pid;
                        
-                       if (startInfo.RedirectStandardInput == true) {
-                               MonoIO.Close (stdin_rd, out error);
-                               process.input_stream = new StreamWriter (new MonoSyncFileStream (stdin_wr, FileAccess.Write, true, 8192), Console.Out.Encoding);
-                               process.input_stream.AutoFlush = true;
+                       if (startInfo.RedirectStandardInput) {
+                               //
+                               // FIXME: The descriptor needs to be closed but due to wapi io-layer
+                               // not coping with duplicated descriptors any StandardInput write fails
+                               //
+                               // MonoIO.Close (stdin_read, out error);
+
+#if MOBILE
+                               var stdinEncoding = Encoding.Default;
+#else
+                               var stdinEncoding = Console.InputEncoding;
+#endif
+                               process.input_stream = new StreamWriter (new FileStream (new SafeFileHandle (stdin_write, false), FileAccess.Write, 8192, false), stdinEncoding) {
+                                       AutoFlush = true
+                               };
                        }
 
-                       Encoding stdoutEncoding = startInfo.StandardOutputEncoding ?? Console.Out.Encoding;
-                       Encoding stderrEncoding = startInfo.StandardErrorEncoding ?? Console.Out.Encoding;
+                       if (startInfo.RedirectStandardOutput) {
+                               MonoIO.Close (stdout_write, out error);
 
-                       if (startInfo.RedirectStandardOutput == true) {
-                               MonoIO.Close (stdout_wr, out error);
-                               process.output_stream = new StreamReader (new MonoSyncFileStream (process.stdout_rd, FileAccess.Read, true, 8192), stdoutEncoding, true, 8192);
+                               Encoding stdoutEncoding = startInfo.StandardOutputEncoding ?? Console.Out.Encoding;
+
+                               process.output_stream = new StreamReader (new FileStream (new SafeFileHandle (stdout_read, false), FileAccess.Read, 8192, false), stdoutEncoding, true, 8192);
                        }
 
-                       if (startInfo.RedirectStandardError == true) {
-                               MonoIO.Close (stderr_wr, out error);
-                               process.error_stream = new StreamReader (new MonoSyncFileStream (process.stderr_rd, FileAccess.Read, true, 8192), stderrEncoding, true, 8192);
+                       if (startInfo.RedirectStandardError) {
+                               MonoIO.Close (stderr_write, out error);
+
+                               Encoding stderrEncoding = startInfo.StandardErrorEncoding ?? Console.Out.Encoding;
+
+                               process.error_stream = new StreamReader (new FileStream (new SafeFileHandle (stderr_read, false), FileAccess.Read, 8192, false), stderrEncoding, true, 8192);
                        }
 
                        process.StartExitCallbackIfNeeded ();
 
-                       return(ret);
+                       return true;
                }
 
                // Note that ProcInfo.Password must be freed.
@@ -1257,7 +1282,13 @@ namespace System.Diagnostics {
                                                return false;
                                }
                        }
-                       return WaitForExit_internal (process_handle, ms);
+
+                       bool exited = WaitForExit_internal (process_handle, ms);
+
+                       if (exited)
+                               OnExited ();
+
+                       return exited;
                }
 
                /* Waits up to ms milliseconds for process 'handle' to 
@@ -1315,7 +1346,7 @@ namespace System.Diagnostics {
                }
 
                [StructLayout (LayoutKind.Sequential)]
-               sealed class ProcessAsyncReader
+               sealed class ProcessAsyncReader : IThreadPoolWorkItem
                {
                        /*
                           The following fields match those of SocketAsyncResult.
@@ -1352,7 +1383,7 @@ namespace System.Diagnostics {
                        bool err_out; // true -> stdout, false -> stderr
                        internal int error;
                        public int operation = 8; // MAGIC NUMBER: see Socket.cs:AsyncOperation
-                       public object ares;
+                       public AsyncResult async_result;
                        public int EndCalled;
 
                        // These fields are not in SocketAsyncResult
@@ -1454,8 +1485,21 @@ namespace System.Diagnostics {
                        }
 
                        public void Close () {
+                               RemoveFromIOThreadPool (handle);
                                stream.Close ();
                        }
+
+                       [MethodImplAttribute(MethodImplOptions.InternalCall)]
+                       extern static void RemoveFromIOThreadPool (IntPtr handle);
+
+                       void IThreadPoolWorkItem.ExecuteWorkItem()
+                       {
+                               async_result.Invoke ();
+                       }
+
+                       void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae)
+                       {
+                       }
                }
 
                AsyncModes async_mode;
@@ -1560,7 +1604,7 @@ namespace System.Diagnostics {
                                // dispose all managed resources.
                                if(disposing) {
                                        // Do stuff here
-                                       lock (this) {
+                                       lock (thisLock) {
                                                /* These have open FileStreams on the pipes we are about to close */
                                                if (async_output != null)
                                                        async_output.Close ();
@@ -1568,17 +1612,18 @@ namespace System.Diagnostics {
                                                        async_error.Close ();
 
                                                if (input_stream != null) {
-                                                       input_stream.Close();
+                                                       if (!input_stream_exposed)
+                                                               input_stream.Close ();
                                                        input_stream = null;
                                                }
-
                                                if (output_stream != null) {
-                                                       output_stream.Close();
+                                                       if (!output_stream_exposed)
+                                                               output_stream.Close ();
                                                        output_stream = null;
                                                }
-
                                                if (error_stream != null) {
-                                                       error_stream.Close();
+                                                       if (!error_stream_exposed)
+                                                               error_stream.Close ();
                                                        error_stream = null;
                                                }
                                        }
@@ -1586,7 +1631,7 @@ namespace System.Diagnostics {
                                
                                // Release unmanaged resources
 
-                               lock(this) {
+                               lock (thisLock) {
                                        if(process_handle!=IntPtr.Zero) {
                                                Process_free_internal(process_handle);
                                                process_handle=IntPtr.Zero;
@@ -1604,15 +1649,31 @@ namespace System.Diagnostics {
                static void CBOnExit (object state, bool unused)
                {
                        Process p = (Process) state;
-                       p.already_waiting = false;
+
+                       if (!p.IsExitCallbackPending ())
+                               return;
+
+                       if (!p.HasExited) {
+                               p.UnregisterExitCallback ();
+                               p.StartExitCallbackIfNeeded ();
+                               return;
+                       }
+
                        p.OnExited ();
                }
 
+               int on_exited_called = 0;
+
                protected void OnExited() 
                {
                        if (exited_event == null)
                                return;
 
+                       if (on_exited_called != 0 || Interlocked.CompareExchange (ref on_exited_called, 1, 0) != 0)
+                               return;
+
+                       UnregisterExitCallback ();
+
                        if (synchronizingObject == null) {
                                foreach (EventHandler d in exited_event.GetInvocationList ()) {
                                        try {