diff --git a/examples/UnixFDClient.cs b/examples/UnixFDClient.cs new file mode 100644 index 0000000..d4ff500 --- /dev/null +++ b/examples/UnixFDClient.cs @@ -0,0 +1,142 @@ +// Copyright 2017 Steffen Kiess +// This software is made available under the MIT License +// See COPYING for details + +using System; + +using DBus; +using org.freedesktop.DBus; + +using Mono.Unix; +using Mono.Unix.Native; + +class SignalsImpl : Signals { + public event Action GotFD; + + public void CallGotFD (UnixFD fd) { + var handler = GotFD; + if (handler != null) + handler (fd); + } +} + +public class ManagedDBusTest +{ + public static void Main (string[] args) + { + Bus conn; + + if (args.Length == 0) + conn = Bus.Session; + else { + if (args[0] == "--session") + conn = Bus.Session; + else if (args[0] == "--system") + conn = Bus.System; + else + conn = Bus.Open (args[0]); + } + + IBus bus = conn.GetObject ("org.freedesktop.DBus", new ObjectPath ("/org/freedesktop/DBus")); + Console.WriteLine (bus.ListNames ().Length); + + var obj = conn.GetObject (Constants.BusName, Constants.ObjectPath); + var obj2 = conn.GetObject (Constants.BusName, Constants.ObjectPath); + var objIntr = conn.GetObject (Constants.BusName, Constants.ObjectPath); + obj.Ping (); + Console.WriteLine (obj.GetBytes (3).Length); + + Console.WriteLine ("conn.UnixFDSupported = " + conn.UnixFDSupported); + if (!conn.UnixFDSupported) + return; + + using (var disposableList = new DisposableList ()) { + var res = obj.GetFD (disposableList, false); + Console.WriteLine ("Got FD:"); + Mono.Unix.Native.Stdlib.system ("ls -l /proc/$PPID/fd/" + res.Handle); + } + using (var disposableList = new DisposableList ()) { + var res = obj.GetFDList (disposableList, false); + Console.WriteLine ("Got FDs:"); + foreach (var fd in res) + Mono.Unix.Native.Stdlib.system ("ls -l /proc/$PPID/fd/" + fd.Handle); + } + using (var disposableList = new DisposableList ()) { + var res = (UnixFD[]) obj.GetFDListVariant (disposableList, false); + Console.WriteLine ("Got FDs as variant:"); + foreach (var fd in res) + Mono.Unix.Native.Stdlib.system ("ls -l /proc/$PPID/fd/" + fd.Handle); + } + + using (var disposableList = new DisposableList ()) { + try { + obj.GetFD (disposableList, true); + throw new Exception ("Expected an exception"); + } catch (Exception e) { + if (!e.Message.Contains ("Throwing an exception after creating a UnixFD object")) + throw; + } + } + using (var disposableList = new DisposableList ()) { + try { + obj.GetFDList (disposableList, true); + throw new Exception ("Expected an exception"); + } catch (Exception e) { + if (!e.Message.Contains ("Throwing an exception after creating a UnixFD object")) + throw; + } + } + using (var disposableList = new DisposableList ()) { + try { + obj.GetFDListVariant (disposableList, true); + throw new Exception ("Expected an exception"); + } catch (Exception e) { + if (!e.Message.Contains ("Throwing an exception after creating a UnixFD object")) + throw; + } + } + + // Check whether this leaks an FD + obj.GetFD (null, false); + obj.GetFDList (null, false); + obj.GetFDListVariant (null, false); + try { obj.GetFD (null, true); } catch {} + try { obj.GetFDList (null, true); } catch {} + try { obj.GetFDListVariant (null, true); } catch {} + obj2.GetFD (false); + obj2.GetFDList (false); + obj2.GetFDListVariant (false); + try { obj2.GetFD (true); } catch {} + try { obj2.GetFDList (true); } catch {} + try { obj2.GetFDListVariant (true); } catch {} + + var fd_ = Syscall.open ("/dev/null", OpenFlags.O_RDWR, 0); + if (fd_ < 0) + UnixMarshal.ThrowExceptionForLastError (); + using (var fd = new UnixFD (fd_)) { + obj.SendFD (fd); + obj.SendFD (fd); + obj.SendFDList (new UnixFD[] { fd, fd }); + obj.SendFDListVariant (new UnixFD[] { fd, fd }); + + var impl = new SignalsImpl (); + var spath = new ObjectPath ("/mono_dbus_sharp_test/Signals"); + conn.Register (spath, impl); + obj.RegisterSignalInterface (conn.UniqueName, spath); + impl.CallGotFD (fd); + } + + Console.WriteLine (objIntr.Introspect ().Length); + + obj.ListOpenFDs (); + Console.WriteLine ("Open FDs:"); + Mono.Unix.Native.Stdlib.system ("ls -l /proc/$PPID/fd/"); + } +} + +// vim: noexpandtab +// Local Variables: +// tab-width: 4 +// c-basic-offset: 4 +// indent-tabs-mode: t +// End: diff --git a/examples/UnixFDInterface.cs b/examples/UnixFDInterface.cs new file mode 100644 index 0000000..68ac4ae --- /dev/null +++ b/examples/UnixFDInterface.cs @@ -0,0 +1,61 @@ +// Copyright 2017 Steffen Kiess +// This software is made available under the MIT License +// See COPYING for details + +using System; +using DBus; + +public static class Constants { + public const string BusName = "mono_dbus_sharp_test.UnixFDService"; + public static readonly ObjectPath ObjectPath = new ObjectPath ("/mono_dbus_sharp_test/UnixFDService"); +} + +[DBus.Interface ("mono_dbus_sharp_test.UnixFDService")] +public interface Interface { + void Ping (); + + void ListOpenFDs (); + + byte[] GetBytes (int len); + + UnixFD GetFD (DisposableList disposableList, bool throwError); + UnixFD[] GetFDList (DisposableList disposableList, bool throwError); + object GetFDListVariant (DisposableList disposableList, bool throwError); + + void SendFD (UnixFD fd); + void SendFDList (UnixFD[] fds); + void SendFDListVariant (object fds); + + void RegisterSignalInterface (string busName, ObjectPath path); +} + +[DBus.Interface ("mono_dbus_sharp_test.UnixFDService")] +public interface Interface2 { + void Ping (); + + void ListOpenFDs (); + + byte[] GetBytes (int len); + + UnixFD GetFD (bool throwError); + UnixFD[] GetFDList (bool throwError); + object GetFDListVariant (bool throwError); + + void SendFD (UnixFD fd); + void SendFDList (UnixFD[] fds); + void SendFDListVariant (object fds); + + void RegisterSignalInterface (string busName, ObjectPath path); +} + +[DBus.Interface ("mono_dbus_sharp_test.UnixFDSignals")] +public interface Signals { + event Action GotFD; +} + +// vim: noexpandtab +// Local Variables: +// tab-width: 4 +// c-basic-offset: 4 +// indent-tabs-mode: t +// End: diff --git a/examples/UnixFDService.cs b/examples/UnixFDService.cs new file mode 100644 index 0000000..4596197 --- /dev/null +++ b/examples/UnixFDService.cs @@ -0,0 +1,137 @@ +// Copyright 2017 Steffen Kiess +// This software is made available under the MIT License +// See COPYING for details + +/* +PATH="$HOME/mono-master/bin:$PATH" ./autogen.sh && make -j8 +make && ( cd examples && mcs -g UnixFDService.cs UnixFDInterface.cs -r ../src/dbus-sharp.dll -r Mono.Posix ) && MONO_PATH=src $HOME/mono-master/bin/mono --debug examples/UnixFDService.exe +make && ( cd examples && mcs -g UnixFDClient.cs UnixFDInterface.cs -r ../src/dbus-sharp.dll -r Mono.Posix ) && MONO_PATH=src $HOME/mono-master/bin/mono --debug examples/UnixFDClient.exe + +python3 +import dbus; dbus.SessionBus().get_object('mono_dbus_sharp_test.UnixFDService', '/mono_dbus_sharp_test/UnixFDService').GetBytes(3) +import dbus; f = dbus.SessionBus().get_object('mono_dbus_sharp_test.UnixFDService', '/mono_dbus_sharp_test/UnixFDService').GetFD () +*/ + +using System; + +using Mono.Unix; +using Mono.Unix.Native; + +using DBus; +using org.freedesktop.DBus; + +public class Impl : Interface { + Connection conn; + + public Impl (Connection conn) + { + this.conn = conn; + } + + public void Ping () + { + } + + public void ListOpenFDs () + { + Console.WriteLine ("Open FDs:"); + Mono.Unix.Native.Stdlib.system ("ls -l /proc/$PPID/fd/"); + } + + public UnixFD GetFD (DisposableList disposableList, bool throwError) + { + var fd_ = Syscall.open ("/dev/null", OpenFlags.O_RDWR, 0); + if (fd_ < 0) + UnixMarshal.ThrowExceptionForLastError (); + var fd = new UnixFD (fd_); + disposableList.Add (fd); + + if (throwError) + throw new Exception ("Throwing an exception after creating a UnixFD object"); + + return fd; + } + + public UnixFD[] GetFDList (DisposableList disposableList, bool throwError) + { + return new UnixFD[] { + GetFD (disposableList, false), GetFD (disposableList, throwError), GetFD (disposableList, false) + }; + } + + public object GetFDListVariant (DisposableList disposableList, bool throwError) + { + return GetFDList (disposableList, throwError); + } + + public byte[] GetBytes (int len) + { + return new byte[len]; + } + + public void SendFD (UnixFD fd) + { + Console.WriteLine ("Got FD as parameter:"); + Mono.Unix.Native.Stdlib.system ("ls -l /proc/$PPID/fd/" + fd.Handle); + } + + public void SendFDList (UnixFD[] fds) + { + Console.WriteLine ("Got FDs as parameter:"); + foreach (var fd in fds) + Mono.Unix.Native.Stdlib.system ("ls -l /proc/$PPID/fd/" + fd.Handle); + } + + public void SendFDListVariant (object fds) + { + Console.WriteLine ("Got FDs as variant parameter:"); + foreach (var fd in (UnixFD[]) fds) + Mono.Unix.Native.Stdlib.system ("ls -l /proc/$PPID/fd/" + fd.Handle); + } + + public void RegisterSignalInterface (string busName, ObjectPath path) + { + Console.WriteLine ("Register for GotFD event at {0} / {1}", busName, path); + conn.GetObject (busName, path).GotFD += fd => { + Console.WriteLine ("Got FD from signal:"); + Mono.Unix.Native.Stdlib.system ("ls -l /proc/$PPID/fd/" + fd.Handle); + }; + } +} + +public class UnixFDService +{ + public static void Main (string[] args) + { + Bus conn; + + if (args.Length == 0) + conn = Bus.Session; + else { + if (args[0] == "--session") + conn = Bus.Session; + else if (args[0] == "--system") + conn = Bus.System; + else + conn = Bus.Open (args[0]); + } + + conn.Register (Constants.ObjectPath, new Impl (conn)); + + if (conn.RequestName (Constants.BusName) != org.freedesktop.DBus.RequestNameReply.PrimaryOwner) + throw new Exception ("Could not request name"); + + Console.WriteLine ("Waiting for requests..."); + + while (conn.IsConnected) { + conn.Iterate (); + } + } +} + +// vim: noexpandtab +// Local Variables: +// tab-width: 4 +// c-basic-offset: 4 +// indent-tabs-mode: t +// End: diff --git a/src/Authentication.cs b/src/Authentication.cs index cd23945..a2152a6 100644 --- a/src/Authentication.cs +++ b/src/Authentication.cs @@ -324,6 +324,8 @@ public virtual bool Run (IEnumerable commands) class SaslClient : SaslPeer { public string Identity = String.Empty; + public bool TransportSupportsUnixFD = false; + public bool UnixFDSupported = false; //static Regex rejectedRegex = new Regex (@"^REJECTED(\s+(\w+))*$"); @@ -387,6 +389,14 @@ public override IEnumerator GetEnumerator () else ActualId = UUID.Parse (reply[1]); + if (TransportSupportsUnixFD) { + yield return new AuthCommand ("NEGOTIATE_UNIX_FD"); + if (!replies.MoveNext ()) + yield break; + reply = replies.Current; + UnixFDSupported = reply[0] == "AGREE_UNIX_FD"; + } + yield return new AuthCommand ("BEGIN"); yield break; } diff --git a/src/BusObject.cs b/src/BusObject.cs index f8509b5..865c52a 100644 --- a/src/BusObject.cs +++ b/src/BusObject.cs @@ -80,6 +80,10 @@ public void ToggleSignal (string iface, string member, Delegate dlg, bool adding } public void SendSignal (string iface, string member, string inSigStr, MessageWriter writer, Type retType, out Exception exception) + { + SendSignal (iface, member, inSigStr, writer, retType, null, out exception); + } + internal void SendSignal (string iface, string member, string inSigStr, MessageWriter writer, Type retType, DisposableList disposableList, out Exception exception) { exception = null; @@ -100,6 +104,10 @@ public void SendSignal (string iface, string member, string inSigStr, MessageWri } public MessageReader SendMethodCall (string iface, string member, string inSigStr, MessageWriter writer, Type retType, out Exception exception) + { + return SendMethodCall (iface, member, inSigStr, writer, retType, null, out exception); + } + public MessageReader SendMethodCall (string iface, string member, string inSigStr, MessageWriter writer, Type retType, DisposableList disposableList, out Exception exception) { if (string.IsNullOrEmpty (bus_name)) throw new ArgumentNullException ("bus_name"); @@ -137,7 +145,10 @@ public MessageReader SendMethodCall (string iface, string member, string inSigSt } #endif - Message retMsg = conn.SendWithReplyAndBlock (callMsg); + Message retMsg = conn.SendWithReplyAndBlock (callMsg, disposableList != null); + if (disposableList != null && retMsg.UnixFDArray != null) + foreach (var fd in retMsg.UnixFDArray.FDs) + disposableList.Add (fd); MessageReader retVal = null; @@ -163,6 +174,10 @@ public MessageReader SendMethodCall (string iface, string member, string inSigSt } public void Invoke (MethodBase methodBase, string methodName, object[] inArgs, out object[] outArgs, out object retVal, out Exception exception) + { + Invoke (methodBase, methodName, inArgs, null, out outArgs, out retVal, out exception); + } + public void Invoke (MethodBase methodBase, string methodName, object[] inArgs, DisposableList disposableList, out object[] outArgs, out object retVal, out Exception exception) { outArgs = new object[0]; retVal = null; diff --git a/src/Connection.cs b/src/Connection.cs index 4a18033..0284e6c 100644 --- a/src/Connection.cs +++ b/src/Connection.cs @@ -29,6 +29,7 @@ public class Connection bool isShared = false; UUID Id = UUID.Zero; bool isAuthenticated = false; + bool unixFDSupported = false; int serial = 0; // STRONG TODO: GET RID OF THAT SHIT @@ -73,6 +74,12 @@ internal bool IsAuthenticated { } } + public bool UnixFDSupported { + get { + return unixFDSupported; + } + } + internal Transport Transport { get { return transport; @@ -145,6 +152,8 @@ void Authenticate () transport.WriteCred (); SaslClient auth = new SaslClient (); + if (transport != null) + auth.TransportSupportsUnixFD = transport.TransportSupportsUnixFD; auth.Identity = transport.AuthString (); auth.stream = transport.Stream; auth.Peer = new SaslPeer (); @@ -162,6 +171,7 @@ void Authenticate () Id = auth.ActualId; isAuthenticated = true; + unixFDSupported = auth.UnixFDSupported; } // Interlocked.Increment() handles the overflow condition for uint correctly, @@ -171,13 +181,13 @@ internal uint GenerateSerial () return (uint)Interlocked.Increment (ref serial); } - internal Message SendWithReplyAndBlock (Message msg) + internal Message SendWithReplyAndBlock (Message msg, bool keepFDs) { - PendingCall pending = SendWithReply (msg); + PendingCall pending = SendWithReply (msg, keepFDs); return pending.Reply; } - internal PendingCall SendWithReply (Message msg) + internal PendingCall SendWithReply (Message msg, bool keepFDs) { msg.ReplyExpected = true; @@ -186,7 +196,7 @@ internal PendingCall SendWithReply (Message msg) // Should we throttle the maximum number of concurrent PendingCalls? // Should we support timeouts? - PendingCall pending = new PendingCall (this); + PendingCall pending = new PendingCall (this, keepFDs); lock (pendingCalls) pendingCalls[msg.Header.Serial] = pending; @@ -211,7 +221,11 @@ internal void DispatchSignals () lock (inbound) { while (inbound.Count != 0) { Message msg = inbound.Dequeue (); - HandleSignal (msg); + try { + HandleSignal (msg); + } finally { + msg.Dispose (); + } } } } @@ -233,54 +247,66 @@ internal virtual void HandleMessage (Message msg) if (msg == null) throw new ArgumentNullException ("msg", "Cannot handle a null message; maybe the bus was disconnected"); - //TODO: Restrict messages to Local ObjectPath? + bool cleanupFDs = true; + try { - { - object field_value = msg.Header[FieldCode.ReplySerial]; - if (field_value != null) { - uint reply_serial = (uint)field_value; - PendingCall pending; + //TODO: Restrict messages to Local ObjectPath? - lock (pendingCalls) { - if (pendingCalls.TryGetValue (reply_serial, out pending)) { - if (pendingCalls.Remove (reply_serial)) - pending.Reply = msg; + { + object field_value = msg.Header[FieldCode.ReplySerial]; + if (field_value != null) { + uint reply_serial = (uint)field_value; + PendingCall pending; - return; + lock (pendingCalls) { + if (pendingCalls.TryGetValue (reply_serial, out pending)) { + if (pendingCalls.Remove (reply_serial)) { + pending.Reply = msg; + if (pending.KeepFDs) + cleanupFDs = false; // caller is responsible for closing FDs + } + + return; + } } - } - //we discard reply messages with no corresponding PendingCall - if (ProtocolInformation.Verbose) - Console.Error.WriteLine ("Unexpected reply message received: MessageType='" + msg.Header.MessageType + "', ReplySerial=" + reply_serial); + //we discard reply messages with no corresponding PendingCall + if (ProtocolInformation.Verbose) + Console.Error.WriteLine ("Unexpected reply message received: MessageType='" + msg.Header.MessageType + "', ReplySerial=" + reply_serial); - return; + return; + } } - } - switch (msg.Header.MessageType) { - case MessageType.MethodCall: - MessageContainer method_call = MessageContainer.FromMessage (msg); - HandleMethodCall (method_call); - break; - case MessageType.Signal: - //HandleSignal (msg); - lock (inbound) - inbound.Enqueue (msg); - break; - case MessageType.Error: - //TODO: better exception handling - MessageContainer error = MessageContainer.FromMessage (msg); - string errMsg = String.Empty; - if (msg.Signature.Value.StartsWith ("s")) { - MessageReader reader = new MessageReader (msg); - errMsg = reader.ReadString (); - } - Console.Error.WriteLine ("Remote Error: Signature='" + msg.Signature.Value + "' " + error.ErrorName + ": " + errMsg); - break; - case MessageType.Invalid: - default: - throw new Exception ("Invalid message received: MessageType='" + msg.Header.MessageType + "'"); + switch (msg.Header.MessageType) { + case MessageType.MethodCall: + MessageContainer method_call = MessageContainer.FromMessage (msg); + HandleMethodCall (method_call); + break; + case MessageType.Signal: + //HandleSignal (msg); + lock (inbound) + inbound.Enqueue (msg); + cleanupFDs = false; // FDs are closed after signal is handled + break; + case MessageType.Error: + //TODO: better exception handling + MessageContainer error = MessageContainer.FromMessage (msg); + string errMsg = String.Empty; + if (msg.Signature.Value.StartsWith ("s")) { + MessageReader reader = new MessageReader (msg); + errMsg = reader.ReadString (); + } + Console.Error.WriteLine ("Remote Error: Signature='" + msg.Signature.Value + "' " + error.ErrorName + ": " + errMsg); + break; + case MessageType.Invalid: + default: + throw new Exception ("Invalid message received: MessageType='" + msg.Header.MessageType + "'"); + } + + } finally { + if (cleanupFDs) + msg.Dispose (); } } @@ -303,9 +329,10 @@ internal void HandleSignal (Message msg) bool compatible = false; Signature inSig, outSig; + bool hasDisposableList; - if (TypeImplementer.SigsForMethod(mi, out inSig, out outSig)) - if (outSig == Signature.Empty && inSig == msg.Signature) + if (TypeImplementer.SigsForMethod(mi, out inSig, out outSig, out hasDisposableList)) + if (outSig == Signature.Empty && inSig == msg.Signature && !hasDisposableList) compatible = true; if (!compatible) { diff --git a/src/DisposableList.cs b/src/DisposableList.cs new file mode 100644 index 0000000..0c4589d --- /dev/null +++ b/src/DisposableList.cs @@ -0,0 +1,37 @@ +// Copyright 2017 Steffen Kiess +// This software is made available under the MIT License +// See COPYING for details + +using System; +using System.Collections.Generic; +using System.IO; + +namespace DBus +{ + public class DisposableList : IDisposable + { + List list = new List (); + + public void Dispose () + { + lock (list) { + foreach (var obj in list) + obj.Dispose (); + } + } + + public void Add (IDisposable obj) + { + if (obj == null) + throw new ArgumentNullException ("obj"); + + list.Add (obj); + } + } +} + +// Local Variables: +// tab-width: 4 +// c-basic-offset: 4 +// indent-tabs-mode: t +// End: diff --git a/src/ExportObject.cs b/src/ExportObject.cs index df9c195..14c2eb3 100644 --- a/src/ExportObject.cs +++ b/src/ExportObject.cs @@ -97,15 +97,18 @@ public virtual void HandleMethodCall (MessageContainer method_call) } Signature inSig, outSig; - TypeImplementer.SigsForMethod (mi, out inSig, out outSig); + bool hasDisposableList; + TypeImplementer.SigsForMethod (mi, out inSig, out outSig, out hasDisposableList); Message msg = method_call.Message; MessageReader msgReader = new MessageReader (msg); MessageWriter retWriter = new MessageWriter (); + using (var disposableList = new DisposableList ()) { + Exception raisedException = null; try { - mCaller (Object, msgReader, msg, retWriter); + mCaller (Object, msgReader, msg, retWriter, disposableList); } catch (Exception e) { raisedException = e; } @@ -142,6 +145,7 @@ public virtual void HandleMethodCall (MessageContainer method_call) replyMsg.Header[FieldCode.Destination] = method_call.Sender; conn.Send (replyMsg); + } } public object Object { diff --git a/src/Introspection.cs b/src/Introspection.cs index d4ddeca..8a62372 100644 --- a/src/Introspection.cs +++ b/src/Introspection.cs @@ -121,11 +121,15 @@ public void WriteType (Type target_type) public void WriteArg (ParameterInfo pi) { + if (pi.Position == 0 && pi.ParameterType == typeof (DisposableList)) + return; WriteArg (pi.ParameterType, Mapper.GetArgumentName (pi), pi.IsOut, false); } public void WriteArgReverse (ParameterInfo pi) { + if (pi.Position == 0 && pi.ParameterType == typeof (DisposableList)) + return; WriteArg (pi.ParameterType, Mapper.GetArgumentName (pi), pi.IsOut, true); } diff --git a/src/Makefile.am b/src/Makefile.am index 5e5db4e..e7c4e99 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -9,6 +9,7 @@ CSFILES = Address.cs \ BusObject.cs \ Connection.cs \ DBus.cs \ + DisposableList.cs \ DProxy.cs \ ExportObject.cs \ Introspection.cs \ @@ -19,6 +20,7 @@ CSFILES = Address.cs \ OSHelpers.cs \ BusException.cs \ AddressEntry.cs \ + UnixFD.cs \ Protocol/DBusStruct.cs \ Protocol/DType.cs \ Protocol/DValue.cs \ @@ -39,9 +41,12 @@ CSFILES = Address.cs \ Protocol/Signature.cs \ Protocol/SocketTransport.cs \ Protocol/Transport.cs \ + Protocol/UnixFDArray.cs \ Transports/UnixNativeTransport.cs \ + Transports/UnixSendmsgTransport.cs \ Transports/UnixTransport.cs \ Unix/UnixStream.cs \ + Unix/UnixMonoStream.cs \ Unix/UnixError.cs \ Unix/UnixSocket.cs \ AssemblyInfo.cs diff --git a/src/Mapper.cs b/src/Mapper.cs index dcfe170..61ed342 100644 --- a/src/Mapper.cs +++ b/src/Mapper.cs @@ -164,6 +164,8 @@ public static Type[] GetTypes (ArgDirection dir, ParameterInfo[] parms) //TODO: consider InOut/Ref for (int i = 0 ; i != parms.Length ; i++) { + if (i == 0 && parms[i].ParameterType == typeof (DisposableList)) + continue; switch (dir) { case ArgDirection.In: //docs say IsIn isn't reliable, and this is indeed true diff --git a/src/Protocol/DType.cs b/src/Protocol/DType.cs index e14c99f..41578ef 100644 --- a/src/Protocol/DType.cs +++ b/src/Protocol/DType.cs @@ -24,6 +24,7 @@ public enum DType : byte String = (byte)'s', ObjectPath = (byte)'o', Signature = (byte)'g', + UnixFD = (byte)'h', Array = (byte)'a', Variant = (byte)'v', diff --git a/src/Protocol/FieldCode.cs b/src/Protocol/FieldCode.cs index a524df2..0956753 100644 --- a/src/Protocol/FieldCode.cs +++ b/src/Protocol/FieldCode.cs @@ -18,6 +18,7 @@ public enum FieldCode : byte Destination, Sender, Signature, + UnixFDs, #if PROTO_REPLY_SIGNATURE ReplySignature, //note: not supported in dbus #endif diff --git a/src/Protocol/Message.cs b/src/Protocol/Message.cs index cbaeb65..48842d6 100644 --- a/src/Protocol/Message.cs +++ b/src/Protocol/Message.cs @@ -8,11 +8,12 @@ namespace DBus.Protocol { - public class Message + public class Message : IDisposable { Header header = new Header (); Connection connection; byte[] body; + UnixFDArray fdArray; public Message () { @@ -23,10 +24,16 @@ public Message () } public static Message FromReceivedBytes (Connection connection, byte[] header, byte[] body) + { + return FromReceivedBytes (connection, header, body, null); + } + + public static Message FromReceivedBytes (Connection connection, byte[] header, byte[] body, UnixFDArray fdArray) { Message message = new Message (); message.connection = connection; message.body = body; + message.fdArray = fdArray; message.SetHeaderData (header); return message; @@ -38,6 +45,12 @@ public byte[] Body { } } + public UnixFDArray UnixFDArray { + get { + return fdArray; + } + } + public Header Header { get { return header; @@ -82,6 +95,13 @@ public void AttachBodyTo (MessageWriter writer) { body = writer.ToArray (); header.Length = (uint)body.Length; + if (writer.fdArray.FDs.Count != 0) { + header[FieldCode.UnixFDs] = (uint) writer.fdArray.FDs.Count; + if (fdArray == null) + fdArray = new UnixFDArray (); + foreach (var fd in writer.fdArray.FDs) + fdArray.FDs.Add (fd); + } } public void HandleHeader (Header headerIn) @@ -100,5 +120,11 @@ public byte[] GetHeaderData () header.WriteHeaderToMessage (writer); return writer.ToArray (); } + + public void Dispose () + { + if (UnixFDArray != null) + UnixFDArray.Dispose (); + } } } diff --git a/src/Protocol/MessageReader.cs b/src/Protocol/MessageReader.cs index 1670df5..25b91c2 100644 --- a/src/Protocol/MessageReader.cs +++ b/src/Protocol/MessageReader.cs @@ -96,6 +96,9 @@ public object ReadValue (Type type) } else if (type == typeof (string)) { readValueCache[type] = () => ReadString (); return ReadString (); + } else if (type == typeof (UnixFD)) { + readValueCache[type] = () => ReadUnixFD (); + return ReadUnixFD (); } else if (type.IsGenericType && (type.GetGenericTypeDefinition () == typeof (Dictionary<,>) || type.GetGenericTypeDefinition() == typeof(IDictionary<,>))) { Type[] genArgs = type.GetGenericArguments (); readValueCache[type] = () => ReadDictionary (genArgs[0], genArgs[1]); @@ -358,6 +361,18 @@ public string ReadString () return val; } + public UnixFD ReadUnixFD () + { + uint index = ReadUInt32 (); + + if (message.UnixFDArray == null) + throw new Exception ("Trying to read a UnixFD value but unix fd transfer not supported"); + if (index >= message.UnixFDArray.FDs.Count) + throw new Exception ("UnixFD index " + index + " out of range (got " + message.UnixFDArray.FDs.Count + " fds)"); + + return message.UnixFDArray.FDs[(int)index]; + } + public ObjectPath ReadObjectPath () { //exactly the same as string diff --git a/src/Protocol/MessageWriter.cs b/src/Protocol/MessageWriter.cs index 11f5329..29801e4 100644 --- a/src/Protocol/MessageWriter.cs +++ b/src/Protocol/MessageWriter.cs @@ -11,10 +11,12 @@ namespace DBus.Protocol { - public sealed class MessageWriter + // Disposing a MessageWriter will close all UnixFDs written to it + public sealed class MessageWriter : IDisposable { EndianFlag endianness; MemoryStream stream; + internal readonly UnixFDArray fdArray; Connection connection; static readonly MethodInfo arrayWriter = typeof (MessageWriter).GetMethod ("WriteArray"); @@ -35,6 +37,13 @@ public MessageWriter (EndianFlag endianness) { this.endianness = endianness; stream = new MemoryStream (); + fdArray = new UnixFDArray (); + } + + public void Dispose () + { + if (fdArray != null) + fdArray.Dispose (); } public Connection Connection { @@ -174,6 +183,13 @@ public void Write (string val) WriteNull (); } + public void Write (UnixFD val) + { + int index = fdArray.FDs.Count; + fdArray.FDs.Add (val); + Write ((uint) index); + } + public void Write (ObjectPath val) { Write (val.Value); @@ -497,6 +513,7 @@ internal void WriteHeaderFields (Dictionary val) Write ((ObjectPath)entry.Value); break; case FieldCode.ReplySerial: + case FieldCode.UnixFDs: Write (Signature.UInt32Sig); Write ((uint)entry.Value); break; diff --git a/src/Protocol/PendingCall.cs b/src/Protocol/PendingCall.cs index 47e92da..f8813a4 100644 --- a/src/Protocol/PendingCall.cs +++ b/src/Protocol/PendingCall.cs @@ -13,12 +13,22 @@ public class PendingCall : IAsyncResult Message reply; ManualResetEvent waitHandle; bool completedSync; + bool keepFDs; public event Action Completed; - public PendingCall (Connection conn) + public PendingCall (Connection conn) : this (conn, false) {} + public PendingCall (Connection conn, bool keepFDs) { this.conn = conn; + this.keepFDs = keepFDs; + } + + internal bool KeepFDs + { + get { + return keepFDs; + } } public Message Reply { diff --git a/src/Protocol/ProtocolInformation.cs b/src/Protocol/ProtocolInformation.cs index 2747516..0e654d5 100644 --- a/src/Protocol/ProtocolInformation.cs +++ b/src/Protocol/ProtocolInformation.cs @@ -64,6 +64,8 @@ public static int GetAlignment (DType dtype) return 8; case DType.String: return 4; + case DType.UnixFD: + return 4; case DType.ObjectPath: return 4; case DType.Signature: diff --git a/src/Protocol/Signature.cs b/src/Protocol/Signature.cs index 66336be..2c56af2 100644 --- a/src/Protocol/Signature.cs +++ b/src/Protocol/Signature.cs @@ -25,6 +25,7 @@ public struct Signature public static readonly Signature UInt16Sig = Allocate (DType.UInt16); public static readonly Signature UInt32Sig = Allocate (DType.UInt32); public static readonly Signature StringSig = Allocate (DType.String); + public static readonly Signature UnixFDSig = Allocate (DType.UnixFD); public static readonly Signature StructBegin = Allocate (DType.StructBegin); public static readonly Signature StructEnd = Allocate (DType.StructEnd); public static readonly Signature ObjectPathSig = Allocate (DType.ObjectPath); @@ -314,6 +315,7 @@ static int GetSize (DType dtype) return 2; case DType.Int32: case DType.UInt32: + case DType.UnixFD: return 4; case DType.Int64: case DType.UInt64: @@ -617,6 +619,9 @@ internal static DType TypeToDType (Type type) if (type == typeof (string)) return DType.String; + if (type == typeof (UnixFD)) + return DType.UnixFD; + if (type == typeof (ObjectPath)) return DType.ObjectPath; @@ -678,6 +683,8 @@ public static DType TypeToDType (Type type) return DType.Double; else if (type == typeof (string)) return DType.String; + else if (type == typeof (UnixFD)) + return DType.UnixFD; else if (type == typeof (ObjectPath)) return DType.ObjectPath; else if (type == typeof (Signature)) @@ -793,6 +800,8 @@ public Type ToType (ref int pos) return typeof (double); case DType.String: return typeof (string); + case DType.UnixFD: + return typeof (UnixFD); case DType.ObjectPath: return typeof (ObjectPath); case DType.Signature: @@ -862,6 +871,9 @@ public static Signature GetSig (Type type) if (type == typeof (string)) return Signature.StringSig; + if (type == typeof (UnixFD)) + return Signature.UnixFDSig; + if (type == typeof (object)) return Signature.VariantSig; @@ -926,6 +938,7 @@ bool SingleType () case DType.Single: case DType.Double: case DType.String: + case DType.UnixFD: case DType.ObjectPath: case DType.Signature: case DType.Variant: diff --git a/src/Protocol/Transport.cs b/src/Protocol/Transport.cs index 2ae8815..910e652 100644 --- a/src/Protocol/Transport.cs +++ b/src/Protocol/Transport.cs @@ -40,7 +40,14 @@ public static Transport Create (AddressEntry entry) case "unix": { if (OSHelpers.PlatformIsUnixoid) { - Transport transport = new UnixNativeTransport (); + Transport transport; + if (UnixSendmsgTransport.Available ()) { + transport = new UnixSendmsgTransport (); + } else { + if (ProtocolInformation.Verbose) + Console.Error.WriteLine ("Warning: Syscall.sendmsg() not available, transfering unix FDs will not work"); + transport = new UnixNativeTransport (); + } transport.Open (entry); return transport; } @@ -153,6 +160,13 @@ internal Message ReadMessage () try { msg = ReadMessageReal (); + if (msg == null) { + if (connection.IsConnected) { + if (ProtocolInformation.Verbose) + Console.Error.WriteLine ("Warning: The dbus daemon closed the connection"); + } + connection.IsConnected = false; + } } catch (IOException e) { if (ProtocolInformation.Verbose) Console.Error.WriteLine (e.Message); @@ -166,7 +180,7 @@ internal Message ReadMessage () return msg; } - int Read (byte[] buffer, int offset, int count) + internal virtual int Read (byte[] buffer, int offset, int count, UnixFDArray fdArray) { int read = 0; while (read < count) { @@ -186,6 +200,7 @@ Message ReadMessageReal () { byte[] header = null; byte[] body = null; + UnixFDArray fdArray = Connection.UnixFDSupported ? new UnixFDArray () : null; int read; @@ -194,7 +209,7 @@ Message ReadMessageReal () readBuffer = new byte[16]; byte[] hbuf = readBuffer; - read = Read (hbuf, 0, 16); + read = Read (hbuf, 0, 16, fdArray); if (read == 0) return null; @@ -235,7 +250,7 @@ Message ReadMessageReal () header = new byte[16 + toRead]; Array.Copy (hbuf, header, 16); - read = Read (header, 16, toRead); + read = Read (header, 16, toRead, fdArray); if (read != toRead) throw new Exception ("Message header length mismatch: " + read + " of expected " + toRead); @@ -244,13 +259,13 @@ Message ReadMessageReal () if (bodyLen != 0) { body = new byte[bodyLen]; - read = Read (body, 0, bodyLen); + read = Read (body, 0, bodyLen, fdArray); if (read != bodyLen) throw new Exception ("Message body length mismatch: " + read + " of expected " + bodyLen); } - Message msg = Message.FromReceivedBytes (Connection, header, body); + Message msg = Message.FromReceivedBytes (Connection, header, body, fdArray); return msg; } @@ -264,5 +279,10 @@ internal virtual void WriteMessage (Message msg) stream.Flush (); } } + + // Returns true if then transport supports unix FDs, even when the + // actual connection doesn't (e.g. because the daemon doesn't support + // it) + internal virtual bool TransportSupportsUnixFD { get { return false; } } } } diff --git a/src/Protocol/UnixFDArray.cs b/src/Protocol/UnixFDArray.cs new file mode 100644 index 0000000..8748a0f --- /dev/null +++ b/src/Protocol/UnixFDArray.cs @@ -0,0 +1,44 @@ +// Copyright 2017 Steffen Kiess +// This software is made available under the MIT License +// See COPYING for details + +using System; +using System.Collections.Generic; +using System.IO; + +namespace DBus.Protocol +{ + public class UnixFDArray : IDisposable + { + public static readonly int MaxFDs = 16; + + List fds = new List (); + + public IList FDs + { + get { + return fds; + } + } + + public void Dispose () + { + for (int i = 0; i < fds.Count; i++) { + fds[i].Dispose (); + } + } + + internal UnixFDArray Clone () { + var res = new UnixFDArray (); + foreach (var fd in FDs) + res.FDs.Add (fd.Clone ()); + return res; + } + } +} + +// Local Variables: +// tab-width: 4 +// c-basic-offset: 4 +// indent-tabs-mode: t +// End: diff --git a/src/Transports/UnixNativeTransport.cs b/src/Transports/UnixNativeTransport.cs index 8d6410f..9f697ad 100644 --- a/src/Transports/UnixNativeTransport.cs +++ b/src/Transports/UnixNativeTransport.cs @@ -2,15 +2,11 @@ // This software is made available under the MIT License // See COPYING for details -//We send BSD-style credentials on all platforms -//Doesn't seem to break Linux (but is redundant there) -//This may turn out to be a bad idea -#define HAVE_CMSGCRED - using System; using System.IO; using System.Text; using System.Runtime.InteropServices; + using DBus.Unix; using DBus.Protocol; @@ -42,52 +38,6 @@ public override void Open (string path, bool @abstract) Stream = new UnixStream (socket); } - //send peer credentials null byte - //different platforms do this in different ways -#if HAVE_CMSGCRED - unsafe void WriteBsdCred () - { - //null credentials byte - byte buf = 0; - - IOVector iov = new IOVector (); - //iov.Base = (IntPtr)(&buf); - iov.Base = &buf; - iov.Length = 1; - - msghdr msg = new msghdr (); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - cmsg cm = new cmsg (); - msg.msg_control = (IntPtr)(&cm); - msg.msg_controllen = (uint)sizeof (cmsg); - cm.hdr.cmsg_len = (uint)sizeof (cmsg); - cm.hdr.cmsg_level = 0xffff; //SOL_SOCKET - cm.hdr.cmsg_type = 0x03; //SCM_CREDS - - int written = socket.SendMsg (&msg, 0); - if (written != 1) - throw new Exception ("Failed to write credentials"); - } -#endif - - public override void WriteCred () - { -#if HAVE_CMSGCRED - try { - WriteBsdCred (); - return; - } catch { - if (ProtocolInformation.Verbose) - Console.Error.WriteLine ("Warning: WriteBsdCred() failed; falling back to ordinary WriteCred()"); - } -#endif - //null credentials byte - byte buf = 0; - Stream.WriteByte (buf); - } - public static byte[] GetSockAddr (string path) { byte[] p = Encoding.Default.GetBytes (path); @@ -140,42 +90,4 @@ internal UnixSocket OpenAbstractUnix (string path) return client; } } - -#if HAVE_CMSGCRED - unsafe struct msghdr - { - public IntPtr msg_name; //optional address - public uint msg_namelen; //size of address - public IOVector *msg_iov; //scatter/gather array - public int msg_iovlen; //# elements in msg_iov - public IntPtr msg_control; //ancillary data, see below - public uint msg_controllen; //ancillary data buffer len - public int msg_flags; //flags on received message - } - - struct cmsghdr - { - public uint cmsg_len; //data byte count, including header - public int cmsg_level; //originating protocol - public int cmsg_type; //protocol-specific type - } - - unsafe struct cmsgcred - { - const int CMGROUP_MAX = 16; - - public int cmcred_pid; //PID of sending process - public uint cmcred_uid; //real UID of sending process - public uint cmcred_euid; //effective UID of sending process - public uint cmcred_gid; //real GID of sending process - public short cmcred_ngroups; //number or groups - public fixed uint cmcred_groups[CMGROUP_MAX]; //groups - } - - struct cmsg - { - public cmsghdr hdr; - public cmsgcred cred; - } -#endif } diff --git a/src/Transports/UnixSendmsgTransport.cs b/src/Transports/UnixSendmsgTransport.cs new file mode 100644 index 0000000..e23aa32 --- /dev/null +++ b/src/Transports/UnixSendmsgTransport.cs @@ -0,0 +1,173 @@ +// Copyright 2017 Steffen Kiess +// This software is made available under the MIT License +// See COPYING for details + +using System; +using System.IO; +using System.Text; +using System.Runtime.InteropServices; + +using DBus.Protocol; + +using Mono.Unix; +using Mono.Unix.Native; + +namespace DBus.Transports +{ + class UnixSendmsgTransport : UnixTransport + { + static bool CheckAvailable () + { + var msghdr = new Msghdr { + msg_iov = new Iovec[] {}, + msg_iovlen = 0, + }; + // sendmsg() should return EBADFD because fd == -1 + // If sendmsg() is not available (e.g. because Mono.Posix is too + // old or this is on a system without sendmsg()), Syscall.sendmsg() + // will throw an exception + Syscall.sendmsg (-1, msghdr, 0); + return true; + } + + public static bool Available () + { + try { + return CheckAvailable (); + } catch { + return false; + } + } + + public UnixSendmsgTransport () + { + SocketHandle = -1; + } + + internal override bool TransportSupportsUnixFD { get { return true; } } + + public override string AuthString () + { + long uid = Mono.Unix.Native.Syscall.geteuid (); + return uid.ToString (); + } + + public override void Open (string path, bool @abstract) + { + if (String.IsNullOrEmpty (path)) + throw new ArgumentException ("path"); + + var addr = new SockaddrUn (path, linuxAbstractNamespace:@abstract); + + SocketHandle = Syscall.socket (UnixAddressFamily.AF_UNIX, UnixSocketType.SOCK_STREAM, 0); + if (SocketHandle == -1) + UnixMarshal.ThrowExceptionForLastError (); + bool success = false; + try { + if (Syscall.connect ((int) SocketHandle, addr) < 0) + UnixMarshal.ThrowExceptionForLastError (); + Stream = new DBus.Unix.UnixMonoStream ((int) SocketHandle); + success = true; + } finally { + if (!success) { + int ret = Syscall.close ((int) SocketHandle); + SocketHandle = -1; + if (ret == -1) + UnixMarshal.ThrowExceptionForLastError (); + } + } + } + + readonly object writeLock = new object (); + + byte[] cmsgBuffer = new byte[Syscall.CMSG_LEN ((ulong)(sizeof (int) * UnixFDArray.MaxFDs))]; + // Might return short reads + unsafe int ReadShort (byte[] buffer, int offset, int length, UnixFDArray fdArray) + { + if (length < 0 || offset < 0 || length + offset < length || length + offset > buffer.Length) + throw new ArgumentException (); + + fixed (byte* ptr = buffer, cmsgPtr = cmsgBuffer) { + var iovecs = new Iovec[] { + new Iovec { + iov_base = (IntPtr) (ptr + offset), + iov_len = (ulong) length, + }, + }; + + var msghdr = new Msghdr { + msg_iov = iovecs, + msg_iovlen = 1, + msg_control = cmsgBuffer, + msg_controllen = cmsgBuffer.Length, + }; + + long r; + do { + r = Syscall.recvmsg ((int) SocketHandle, msghdr, 0); + } while (UnixMarshal.ShouldRetrySyscall ((int) r)); + + for (long cOffset = Syscall.CMSG_FIRSTHDR (msghdr); cOffset != -1; cOffset = Syscall.CMSG_NXTHDR (msghdr, cOffset)) { + var recvHdr = Cmsghdr.ReadFromBuffer (msghdr, cOffset); + if (recvHdr.cmsg_level != UnixSocketProtocol.SOL_SOCKET) + continue; + if (recvHdr.cmsg_type != UnixSocketControlMessage.SCM_RIGHTS) + continue; + var recvDataOffset = Syscall.CMSG_DATA (msghdr, cOffset); + var bytes = recvHdr.cmsg_len - (recvDataOffset - cOffset); + var fdCount = bytes / sizeof (int); + for (int i = 0; i < fdCount; i++) + fdArray.FDs.Add (new UnixFD (((int*) (cmsgPtr + recvDataOffset))[i])); + } + + if ((msghdr.msg_flags & MessageFlags.MSG_CTRUNC) != 0) + throw new Exception ("Control message truncated (probably file descriptors lost)"); + + return (int) r; + } + } + + internal override int Read (byte[] buffer, int offset, int length, UnixFDArray fdArray) + { + if (!Connection.UnixFDSupported || fdArray == null) + return base.Read (buffer, offset, length, fdArray); + + int read = 0; + while (read < length) { + int nread = ReadShort (buffer, offset + read, length - read, fdArray); + if (nread == 0) + break; + read += nread; + } + + if (read > length) + throw new Exception (); + + return read; + } + + internal override unsafe void WriteMessage (Message msg) + { + if (msg.UnixFDArray == null || msg.UnixFDArray.FDs.Count == 0) { + base.WriteMessage (msg); + return; + } + if (!Connection.UnixFDSupported) + throw new Exception ("Attempting to write Unix FDs to a connection which does not support them"); + + lock (writeLock) { + var ms = new MemoryStream (); + msg.Header.GetHeaderDataToStream (ms); + var header = ms.ToArray (); + ((DBus.Unix.UnixMonoStream) Stream).Sendmsg (header, 0, header.Length, msg.Body, 0, msg.Body == null ? 0 : msg.Body.Length, msg.UnixFDArray); + } + } + } +} + +// vim: noexpandtab +// Local Variables: +// tab-width: 4 +// c-basic-offset: 4 +// indent-tabs-mode: t +// End: diff --git a/src/Transports/UnixTransport.cs b/src/Transports/UnixTransport.cs index 048f86a..28f40f5 100644 --- a/src/Transports/UnixTransport.cs +++ b/src/Transports/UnixTransport.cs @@ -2,9 +2,17 @@ // This software is made available under the MIT License // See COPYING for details +//We send BSD-style credentials on all platforms +//Doesn't seem to break Linux (but is redundant there) +//This may turn out to be a bad idea +#define HAVE_CMSGCRED + using System; using System.IO; +using DBus.Protocol; +using DBus.Unix; + namespace DBus.Transports { abstract class UnixTransport : Transport @@ -25,5 +33,89 @@ public override void Open (AddressEntry entry) } public abstract void Open (string path, bool @abstract); + + //send peer credentials null byte + //different platforms do this in different ways +#if HAVE_CMSGCRED + unsafe void WriteBsdCred () + { + //null credentials byte + byte buf = 0; + + IOVector iov = new IOVector (); + //iov.Base = (IntPtr)(&buf); + iov.Base = &buf; + iov.Length = 1; + + msghdr msg = new msghdr (); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + cmsg cm = new cmsg (); + msg.msg_control = (IntPtr)(&cm); + msg.msg_controllen = (uint)sizeof (cmsg); + cm.hdr.cmsg_len = (uint)sizeof (cmsg); + cm.hdr.cmsg_level = 0xffff; //SOL_SOCKET + cm.hdr.cmsg_type = 0x03; //SCM_CREDS + + int written = new UnixSocket ((int) SocketHandle, false).SendMsg (&msg, 0); + if (written != 1) + throw new Exception ("Failed to write credentials"); + } +#endif + + public override void WriteCred () + { +#if HAVE_CMSGCRED + try { + WriteBsdCred (); + return; + } catch { + if (ProtocolInformation.Verbose) + Console.Error.WriteLine ("Warning: WriteBsdCred() failed; falling back to ordinary WriteCred()"); + } +#endif + //null credentials byte + byte buf = 0; + Stream.WriteByte (buf); + } + } + +#if HAVE_CMSGCRED + unsafe struct msghdr + { + public IntPtr msg_name; //optional address + public uint msg_namelen; //size of address + public IOVector *msg_iov; //scatter/gather array + public int msg_iovlen; //# elements in msg_iov + public IntPtr msg_control; //ancillary data, see below + public uint msg_controllen; //ancillary data buffer len + public int msg_flags; //flags on received message + } + + struct cmsghdr + { + public uint cmsg_len; //data byte count, including header + public int cmsg_level; //originating protocol + public int cmsg_type; //protocol-specific type + } + + unsafe struct cmsgcred + { + const int CMGROUP_MAX = 16; + + public int cmcred_pid; //PID of sending process + public uint cmcred_uid; //real UID of sending process + public uint cmcred_euid; //effective UID of sending process + public uint cmcred_gid; //real GID of sending process + public short cmcred_ngroups; //number or groups + public fixed uint cmcred_groups[CMGROUP_MAX]; //groups + } + + struct cmsg + { + public cmsghdr hdr; + public cmsgcred cred; } +#endif } diff --git a/src/TypeImplementer.cs b/src/TypeImplementer.cs index 5e6839f..a4ab9e3 100644 --- a/src/TypeImplementer.cs +++ b/src/TypeImplementer.cs @@ -34,8 +34,8 @@ class TypeImplementer static Dictionary writeMethods = new Dictionary (); static Dictionary typeWriters = new Dictionary (); - static MethodInfo sendMethodCallMethod = typeof (BusObject).GetMethod ("SendMethodCall"); - static MethodInfo sendSignalMethod = typeof (BusObject).GetMethod ("SendSignal"); + static MethodInfo sendMethodCallMethod = typeof (BusObject).GetMethod ("SendMethodCall", new Type[] { typeof (string), typeof (string), typeof (string), typeof (MessageWriter), typeof (Type), typeof (DisposableList), typeof (Exception).MakeByRefType () }); + static MethodInfo sendSignalMethod = typeof (BusObject).GetMethod ("SendSignal", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof (string), typeof (string), typeof (string), typeof (MessageWriter), typeof (Type), typeof (DisposableList), typeof (Exception).MakeByRefType () }, null); static MethodInfo toggleSignalMethod = typeof (BusObject).GetMethod ("ToggleSignal"); static Dictionary hookup_methods = new Dictionary (); @@ -290,7 +290,8 @@ public static void GenHookupMethod (ILGenerator ilg, MethodInfo declMethod, Meth //signature Signature inSig; Signature outSig; - SigsForMethod (declMethod, out inSig, out outSig); + bool hasDisposableList; + SigsForMethod (declMethod, out inSig, out outSig, out hasDisposableList); ilg.Emit (OpCodes.Ldstr, inSig.Value); @@ -300,6 +301,8 @@ public static void GenHookupMethod (ILGenerator ilg, MethodInfo declMethod, Meth foreach (ParameterInfo parm in parms) { + if (hasDisposableList && parm.Position == 0) + continue; if (parm.IsOut) continue; @@ -338,6 +341,12 @@ public static void GenHookupMethod (ILGenerator ilg, MethodInfo declMethod, Meth //the expected return Type GenTypeOf (ilg, retType); + // The DisposableList object to which returned unix FDs will be added + if (hasDisposableList) + ilg.Emit (OpCodes.Ldarg_1); + else + ilg.Emit (OpCodes.Ldnull); + LocalBuilder exc = ilg.DeclareLocal (typeof (Exception)); ilg.Emit (OpCodes.Ldloca_S, exc); @@ -402,16 +411,21 @@ public static void GenHookupMethod (ILGenerator ilg, MethodInfo declMethod, Meth } - public static bool SigsForMethod (MethodInfo mi, out Signature inSig, out Signature outSig) + public static bool SigsForMethod (MethodInfo mi, out Signature inSig, out Signature outSig, out bool hasDisposableList) { inSig = Signature.Empty; outSig = Signature.Empty; + hasDisposableList = false; + bool first = true; foreach (ParameterInfo parm in mi.GetParameters ()) { - if (parm.IsOut) + if (first && !parm.IsOut && parm.ParameterType == typeof (DisposableList)) + hasDisposableList = true; + else if (parm.IsOut) outSig += Signature.GetSig (parm.ParameterType.GetElementType ()); else inSig += Signature.GetSig (parm.ParameterType); + first = false; } outSig += Signature.GetSig (mi.ReturnType); @@ -454,7 +468,7 @@ internal static MethodCaller GenCaller (MethodInfo target) internal static DynamicMethod GenReadMethod (MethodInfo target) { - Type[] parms = new Type[] { typeof (object), typeof (MessageReader), typeof (Message), typeof (MessageWriter) }; + Type[] parms = new Type[] { typeof (object), typeof (MessageReader), typeof (Message), typeof (MessageWriter), typeof (DisposableList) }; DynamicMethod hookupMethod = new DynamicMethod ("Caller", typeof (void), parms, typeof (MessageReader)); Gen (hookupMethod, target); return hookupMethod; @@ -473,9 +487,13 @@ static void Gen (DynamicMethod hookupMethod, MethodInfo declMethod) Dictionary locals = new Dictionary (); foreach (ParameterInfo parm in parms) { - Type parmType = parm.ParameterType; + if (parm.Position == 0 && !parm.IsOut && parmType == typeof (DisposableList)) { + ilg.Emit (OpCodes.Ldarg, 4); // disposableList + continue; + } + if (parm.IsOut) { LocalBuilder parmLocal = ilg.DeclareLocal (parmType.GetElementType ()); locals[parm] = parmLocal; @@ -567,5 +585,5 @@ static void GenTypeOf (ILGenerator ilg, Type t) internal delegate void TypeWriter (MessageWriter writer, T value); - internal delegate void MethodCaller (object instance, MessageReader rdr, Message msg, MessageWriter ret); + internal delegate void MethodCaller (object instance, MessageReader rdr, Message msg, MessageWriter ret, DisposableList disposableList); } diff --git a/src/Unix/UnixMonoStream.cs b/src/Unix/UnixMonoStream.cs new file mode 100644 index 0000000..1dc1f17 --- /dev/null +++ b/src/Unix/UnixMonoStream.cs @@ -0,0 +1,237 @@ +// Copyright 2017 Steffen Kiess +// This software is made available under the MIT License +// See COPYING for details + +using System; +using System.IO; +using System.Runtime.InteropServices; + +using Mono.Unix; +using Mono.Unix.Native; + +namespace DBus.Unix +{ + sealed class UnixMonoStream : Stream + { + int fd; + + public UnixMonoStream (int fd) + { + this.fd = fd; + } + + public override bool CanRead + { + get { + return true; + } + } + + public override bool CanSeek + { + get { + return false; + } + } + + public override bool CanWrite + { + get { + return true; + } + } + + public override long Length + { + get { + throw new NotImplementedException ("Seeking is not implemented"); + } + } + + public override long Position + { + get { + throw new NotImplementedException ("Seeking is not implemented"); + } set { + throw new NotImplementedException ("Seeking is not implemented"); + } + } + + public override long Seek (long offset, SeekOrigin origin) + { + throw new NotImplementedException ("Seeking is not implemented"); + } + + public override void SetLength (long value) + { + throw new NotImplementedException ("Not implemented"); + } + + public override void Flush () + { + } + + private void AssertValidBuffer (byte[] buffer, long offset, long length) + { + if (buffer == null) + throw new ArgumentNullException ("buffer"); + if (offset < 0) + throw new ArgumentOutOfRangeException ("offset", "< 0"); + if (length < 0) + throw new ArgumentOutOfRangeException ("length", "< 0"); + if (offset > buffer.LongLength) + throw new ArgumentException ("destination offset is beyond array size"); + if (offset > (buffer.LongLength - length)) + throw new ArgumentException ("would overrun buffer"); + } + + public override unsafe int Read (byte[] buffer, int offset, int length) + { + AssertValidBuffer (buffer, offset, length); + + long r = 0; + fixed (byte* buf = buffer) { + do { + r = Syscall.read (fd, buf + offset, (ulong) length); + } while (UnixMarshal.ShouldRetrySyscall ((int) r)); + if (r < 0) + UnixMarshal.ThrowExceptionForLastError (); + return (int) r; + } + } + + public override unsafe void Write (byte[] buffer, int offset, int length) + { + AssertValidBuffer (buffer, offset, length); + + int pos = 0; + long r = 0; + fixed (byte* buf = buffer) { + while (pos < length) { + do { + r = Syscall.write (fd, buf + offset + pos, (ulong) length); + } while (UnixMarshal.ShouldRetrySyscall ((int) r)); + if (r < 0) + UnixMarshal.ThrowExceptionForLastError (); + pos += (int) r; + } + } + } + + public override void Close () + { + if (fd != -1) { + int ret = Syscall.close (fd); + fd = -1; + if (ret == -1) + UnixMarshal.ThrowExceptionForLastError (); + } + base.Close (); + } + + // Send the two buffers and the FDs using sendmsg(), don't handle short writes + // length1 + length2 must not be 0 + public unsafe long SendmsgShort (byte[] buffer1, long offset1, long length1, + byte[] buffer2, long offset2, long length2, + DBus.Protocol.UnixFDArray fds) + { + //Console.WriteLine ("SendmsgShort (X, {0}, {1}, {2}, {3}, {4}, {5})", offset1, length1, buffer2 == null ? "-" : "Y", offset2, length2, fds == null ? "-" : "" + fds.FDs.Count); + AssertValidBuffer (buffer1, offset1, length1); + if (buffer2 == null) { + if (length2 != 0) + throw new ArgumentOutOfRangeException ("length2", "!= 0 while buffer2 == null"); + offset2 = 0; + } else { + AssertValidBuffer (buffer2, offset2, length2); + } + + fixed (byte* ptr1 = buffer1, ptr2 = buffer2) { + var iovecs = new Iovec[] { + new Iovec { + iov_base = (IntPtr) (ptr1 + offset1), + iov_len = (ulong) length1, + }, + new Iovec { + iov_base = (IntPtr) (ptr2 + offset2), + iov_len = (ulong) length2, + }, + }; + /* Simulate short writes + if (iovecs[0].iov_len == 0) { + iovecs[1].iov_len = Math.Min (iovecs[1].iov_len, 5); + } else { + iovecs[0].iov_len = Math.Min (iovecs[0].iov_len, 5); + iovecs[1].iov_len = 0; + } + */ + byte[] cmsg = null; + + // Create copy of FDs to prevent the user from Dispose()ing the + // FDs in another thread between writing the FDs into the cmsg + // buffer and calling sendmsg() + using (var fds2 = fds == null ? null : fds.Clone ()) { + int fdCount = fds2 == null ? 0 : fds2.FDs.Count; + if (fdCount != 0) { + // Create one SCM_RIGHTS control message + cmsg = new byte[Syscall.CMSG_SPACE ((uint) fdCount * sizeof (int))]; + } + var msghdr = new Msghdr { + msg_iov = iovecs, + msg_iovlen = length2 == 0 ? 1 : 2, + msg_control = cmsg, + msg_controllen = cmsg == null ? 0 : cmsg.Length, + }; + if (fdCount != 0) { + var hdr = new Cmsghdr { + cmsg_len = (long) Syscall.CMSG_LEN ((uint) fdCount * sizeof (int)), + cmsg_level = UnixSocketProtocol.SOL_SOCKET, + cmsg_type = UnixSocketControlMessage.SCM_RIGHTS, + }; + hdr.WriteToBuffer (msghdr, 0); + var dataOffset = Syscall.CMSG_DATA (msghdr, 0); + fixed (byte* ptr = cmsg) { + for (int i = 0; i < fdCount; i++) + ((int*) (ptr + dataOffset))[i] = fds2.FDs[i].Handle; + } + } + long r; + do { + r = Syscall.sendmsg (fd, msghdr, MessageFlags.MSG_NOSIGNAL); + } while (UnixMarshal.ShouldRetrySyscall ((int) r)); + if (r < 0) + UnixMarshal.ThrowExceptionForLastError (); + if (r == 0) + throw new Exception ("sendmsg() returned 0"); + return r; + } + } + } + + // Send the two buffers and the FDs using sendmsg(), handle short writes + public unsafe void Sendmsg (byte[] buffer1, long offset1, long length1, + byte[] buffer2, long offset2, long length2, + DBus.Protocol.UnixFDArray fds) + { + //SendmsgShort (buffer1, offset1, length1, buffer2, offset2, length2, fds); return; + long bytes_overall = (long) length1 + length2; + long written = 0; + while (written < bytes_overall) { + if (written >= length1) { + long written2 = written - length1; + written += SendmsgShort (buffer2, offset2 + written2, length2 - written2, null, 0, 0, written == 0 ? fds : null); + } else { + written += SendmsgShort (buffer1, offset1 + written, length1 - written, buffer2, offset2, length2, written == 0 ? fds : null); + } + } + if (written != bytes_overall) + throw new Exception ("written != bytes_overall"); + } + } +} + +// vim: noexpandtab +// Local Variables: +// tab-width: 4 +// c-basic-offset: 4 +// indent-tabs-mode: t +// End: diff --git a/src/UnixFD.cs b/src/UnixFD.cs new file mode 100644 index 0000000..2c2cf5d --- /dev/null +++ b/src/UnixFD.cs @@ -0,0 +1,79 @@ +// Copyright 2017 Steffen Kiess +// This software is made available under the MIT License +// See COPYING for details + +using System; +using System.Collections.Generic; +using System.IO; + +using Mono.Unix; +using Mono.Unix.Native; + +namespace DBus +{ + public class UnixFD : IDisposable + { + object lck = new object (); + int fd = -1; + + public UnixFD (int fd) + { + this.fd = fd; + } + + public void Dispose () + { + lock (lck) { + if (fd != -1) { + int r; + // Don't retry close() on EINTR, on a lot of systems (e.g. Linux) the FD will be already closed when EINTR is returned, see https://lwn.net/Articles/576478/ + r = DBus.Unix.UnixSocket.close (fd); + fd = -1; + + if (r < 0) + UnixMarshal.ThrowExceptionForLastError (); + } + } + } + + public override string ToString () + { + lock (lck) { + if (fd == -1) + return "UnixFD (disposed)"; + return "UnixFD (" + fd + ")"; + } + } + + // The caller has to make sure that the FD does not get closed between + // calling UnixFD.Handle and using the handle + public int Handle { + get { + lock (lck) { + if (fd == -1) + throw new ObjectDisposedException (GetType ().FullName); + return fd; + } + } + } + + // Return a new UnixFD instance which will be usable after this UnixFD + // has been closed + public UnixFD Clone () { + lock (lck) { + if (fd == -1) + throw new ObjectDisposedException (GetType ().FullName); + int newFd = Syscall.dup (fd); + if (newFd < 0) + UnixMarshal.ThrowExceptionForLastError (); + return new UnixFD (newFd); + } + } + } +} + +// Local Variables: +// tab-width: 4 +// c-basic-offset: 4 +// indent-tabs-mode: t +// End: diff --git a/src/UnixMonoTransport.cs b/src/UnixMonoTransport.cs deleted file mode 100644 index 47326a7..0000000 --- a/src/UnixMonoTransport.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2006 Alp Toker -// This software is made available under the MIT License -// See COPYING for details - -using System; -using System.IO; -using System.Net; -using System.Net.Sockets; - -using Mono.Unix; -using Mono.Unix.Native; - -namespace DBus.Transports -{ - class UnixMonoTransport : UnixTransport - { - protected Socket socket; - - public override void Open (string path, bool @abstract) - { - if (@abstract) - socket = OpenAbstractUnix (path); - else - socket = OpenUnix (path); - - socket.Blocking = true; - SocketHandle = (long)socket.Handle; - //Stream = new UnixStream ((int)socket.Handle); - Stream = new NetworkStream (socket); - } - - //send peer credentials null byte. note that this might not be portable - //there are also selinux, BSD etc. considerations - public override void WriteCred () - { - Stream.WriteByte (0); - } - - public override string AuthString () - { - long uid = UnixUserInfo.GetRealUserId (); - - return uid.ToString (); - } - - protected Socket OpenAbstractUnix (string path) - { - AbstractUnixEndPoint ep = new AbstractUnixEndPoint (path); - - Socket client = new Socket (AddressFamily.Unix, SocketType.Stream, 0); - client.Connect (ep); - - return client; - } - - public Socket OpenUnix (string path) - { - UnixEndPoint remoteEndPoint = new UnixEndPoint (path); - - Socket client = new Socket (AddressFamily.Unix, SocketType.Stream, 0); - client.Connect (remoteEndPoint); - - return client; - } - } -} diff --git a/src/dbus-sharp.csproj b/src/dbus-sharp.csproj index 28d264e..45ee10d 100644 --- a/src/dbus-sharp.csproj +++ b/src/dbus-sharp.csproj @@ -13,7 +13,7 @@ DBus true ..\dbus-sharp.snk - v3.5 + v4.5 True @@ -47,6 +47,7 @@ + @@ -57,6 +58,7 @@ + @@ -77,9 +79,12 @@ + + +