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 {
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;
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;
}
}
}
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 {
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.")]
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.")]
async_mode |= AsyncModes.SyncOutput;
+ output_stream_exposed = true;
return(output_stream);
}
}
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];
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.
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
}
[StructLayout (LayoutKind.Sequential)]
- sealed class ProcessAsyncReader
+ sealed class ProcessAsyncReader : IThreadPoolWorkItem
{
/*
The following fields match those of SocketAsyncResult.
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
}
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;
// 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 ();
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;
}
}
// Release unmanaged resources
- lock(this) {
+ lock (thisLock) {
if(process_handle!=IntPtr.Zero) {
Process_free_internal(process_handle);
process_handle=IntPtr.Zero;
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 {