1 // Copyright 2006 Alp Toker <alp@atoker.com>
2 // Copyright 2007 Versabanq (Adrian Dewhurst <adewhurst@versabanq.com>)
3 // This software is made available under the MIT License
4 // See COPYING for details
7 using System
.Collections
.Generic
;
9 using System
.Reflection
;
15 public enum NameFlag
: uint
18 AllowReplacement
= 0x1,
19 ReplaceExisting
= 0x2,
23 public enum RequestNameReply
: uint
31 public enum ReleaseNameReply
: uint
38 public enum StartReply
: uint
40 Success
= 1, // service was successfully started
41 AlreadyRunning
, // connection already owns the given name
44 public class WvDbus
: IDisposable
46 static readonly string DBusName
= "org.freedesktop.DBus";
47 static readonly string DBusPath
= "/org/freedesktop/DBus";
49 WvLog log
= new WvLog("DBus");
50 public WvBufStream stream { get; private set; }
51 public bool ok { get { return stream.ok; }
}
53 public WvDbus(string address
)
55 handlers
= new List
<Func
<WvDbusMsg
,bool>>();
56 handlers
.Add(default_handler
);
57 stream
= make_stream(address
);
58 stream
.onreadable
+= () => {
62 // write the credential byte (needed for passing unix uids over
63 // the unix domain socket, but anyway, part of the protocol
65 stream
.write(new byte[] { 0 }
);
67 if (stream
.err
!= null)
70 // Run the authentication phase
71 var auth
= new Dbus
.ExternalAuthClient(this, stream
);
74 unique_name
= CallDBusMethod("Hello");
85 static WvBufStream
make_stream(string address
)
88 WvUrl url
= address_to_url(address
);
89 if (url
.method
== "unix")
92 s
= new WvUnix(url
.path
);
94 throw new Exception("No path specified for UNIX transport");
96 else if (url
.method
== "tcp")
98 string host
= url
.host
.or("127.0.0.1");
99 int port
= url
.port
.or(5555);
100 s
= new WvTcp(host
, (ushort)port
);
103 throw new Exception(wv
.fmt("Unknown connection method {0}",
105 return new WvBufStream(s
);
109 uint GenerateSerial()
114 public WvDbusMsg
send_and_wait(WvDbusMsg msg
)
116 WvDbusMsg reply
= null;
118 send(msg
, (r
) => { reply = r; }
);
121 while (reply
== null && ok
)
127 public uint send(WvDbusMsg msg
, Action
<WvDbusMsg
> replyaction
)
129 msg
.ReplyExpected
= true;
130 msg
.serial
= send(msg
);
131 rserial_to_action
[msg
.serial
] = replyaction
;
135 void printmsg(string prefix
, WvBytes hdata
, WvDbusMsg msg
)
137 log
.print(WvLog
.L
.Debug3
,
138 "{0} {1}#{2} ->{3} '{4}'.'{5}' ({6} bytes)\n",
139 prefix
, msg
.type
, msg
.serial
, msg
.dest
,
141 msg
.Body
==null ? 0 : msg
.Body
.Length
);
142 log
.print(WvLog
.L
.Debug4
, "Header:\n{0}", wv
.hexdump(hdata
));
143 log
.print(WvLog
.L
.Debug5
, "Body:\n{0}", wv
.hexdump(msg
.Body
));
146 public uint send(WvDbusMsg msg
)
148 msg
.serial
= GenerateSerial();
149 var hdata
= msg
.GetHeaderData();
151 long len
= hdata
.Length
+ (msg
.Body
!= null ? msg
.Body
.Length
: 0);
152 if (len
> Dbus
.Protocol
.MaxMessageLength
)
154 wv
.fmt("Message length {0} > max {1}",
155 len
, Dbus
.Protocol
.MaxMessageLength
));
157 printmsg(" >>", hdata
, msg
);
159 if (msg
.Body
!= null && msg
.Body
.Length
!= 0)
160 stream
.write(msg
.Body
);
165 WvBuf inbuf
= new WvBuf();
168 void readbytes(int max
, int msec_timeout
)
171 wv
.assert(entrycount
== 1);
173 log
.print(WvLog
.L
.Debug5
, "Reading: have {0} of needed {1}\n",
175 int needed
= max
- inbuf
.used
;
178 if (stream
.wait(msec_timeout
, true, false))
180 WvBytes b
= inbuf
.alloc(needed
);
181 int got
= stream
.read(b
);
182 inbuf
.unalloc(needed
-got
);
189 * You shouldn't use this, as it bypasses normal message processing.
190 * Add a message handler instead.
192 * It exists because it's useful in our unit tests.
194 public WvDbusMsg
readmessage(int msec_timeout
)
196 foreach (int remain
in wv
.until(msec_timeout
))
198 readbytes(16, remain
);
202 int needed
= WvDbusMsg
.bytes_needed(inbuf
.peek(16));
203 readbytes(needed
, remain
);
204 if (inbuf
.used
< needed
)
207 var msg
= new WvDbusMsg(inbuf
.get(needed
));
208 printmsg("<< ", msg
.GetHeaderData(), msg
);
215 bool handlemessage(int msec_timeout
)
217 var m
= readmessage(msec_timeout
);
220 // use ToArray() here in case the list changes while
222 foreach (var handler
in handlers
.ToArray())
226 // if we get here, there was a message but nobody could
227 // handle it. That's weird because our default handler
228 // should handle *everything*.
229 wv
.assert(false, "No default message handler?!");
235 public void handlemessages(int msec_timeout
)
237 while (handlemessage(msec_timeout
) && ok
)
241 public List
<Func
<WvDbusMsg
,bool>> handlers { get; private set; }
243 bool default_handler(WvDbusMsg msg
)
248 if (msg
.rserial
.HasValue
)
250 Action
<WvDbusMsg
> raction
251 = rserial_to_action
.tryget(msg
.rserial
.Value
);
261 case Dbus
.MType
.Error
:
262 //TODO: better exception handling
263 string errMsg
= String
.Empty
;
264 if (msg
.signature
.StartsWith("s")) {
265 errMsg
= msg
.iter().pop();
267 Console
.Error
.WriteLine
268 ("Remote Error: Signature='" + msg
.signature
269 + "' " + msg
.err
+ ": " + errMsg
);
271 case Dbus
.MType
.Signal
:
272 case Dbus
.MType
.MethodCall
:
273 // nothing to do with these by default, so give an error
274 if (msg
.ReplyExpected
)
276 var r
= msg
.err_reply
277 ("org.freedesktop.DBus.Error.UnknownMethod",
278 "Unknown dbus method '{0}'.'{1}'",
279 msg
.ifc
, msg
.method
);
283 case Dbus
.MType
.Invalid
:
285 throw new Exception("Invalid message received: Dbus.MType='" + msg
.type
+ "'");
289 Dictionary
<uint,Action
<WvDbusMsg
>> rserial_to_action
290 = new Dictionary
<uint,Action
<WvDbusMsg
>>();
292 // Standard D-Bus monikers:
293 // unix:path=whatever,guid=whatever
294 // unix:abstract=whatever,guid=whatever
295 // tcp:host=whatever,port=whatever
297 // wv:wvstreams_moniker
298 internal static WvUrl
address_to_url(string s
)
300 string[] parts
= s
.Split(new char[] { ':' }
, 2);
302 if (parts
.Length
< 2)
303 throw new Exception(wv
.fmt("No colon found in '{0}'", s
));
305 string method
= parts
[0];
306 string user
= null, pass
= null, host
= null, path
= null;
313 foreach (string prop
in parts
[1].Split(new char[] { ',' }
, 2))
315 string[] propa
= prop
.Split(new char[] { '=' }
, 2);
317 if (propa
.Length
< 2)
318 throw new Exception(wv
.fmt("No '=' found in '{0}'",
321 string name
= propa
[0];
322 string value = propa
[1];
326 else if (name
== "abstract")
328 else if (name
== "guid")
330 else if (name
== "host")
332 else if (name
== "port")
334 // else ignore it silently; extra properties might be used
335 // in newer versions for backward compatibility
339 return new WvUrl(method
, user
, pass
, host
, port
, path
);
342 const string SYSTEM_BUS_ADDRESS
343 = "unix:path=/var/run/dbus/system_bus_socket";
344 public static string system_bus_address
347 string addr
= wv
.getenv("DBUS_SYSTEM_BUS_ADDRESS");
350 addr
= SYSTEM_BUS_ADDRESS
;
356 public static string session_bus_address
359 return wv
.getenv("DBUS_SESSION_BUS_ADDRESS");
363 // FIXME: might as well cache this
364 public static WvDbus session_bus
{
366 if (session_bus_address
.e())
367 throw new Exception("DBUS_SESSION_BUS_ADDRESS not set");
368 return new WvDbus(session_bus_address
);
372 WvAutoCast
CallDBusMethod(string method
)
374 return CallDBusMethod(method
, "", new byte[0]);
377 WvAutoCast
CallDBusMethod(string method
, string param
)
379 WvDbusWriter w
= new WvDbusWriter();
382 return CallDBusMethod(method
, "s", w
.ToArray());
385 WvAutoCast
CallDBusMethod(string method
, string p1
, uint p2
)
387 WvDbusWriter w
= new WvDbusWriter();
391 return CallDBusMethod(method
, "su", w
.ToArray());
394 WvAutoCast
CallDBusMethod(string method
, string sig
,
397 var call
= new WvDbusCall(DBusName
, DBusPath
,
398 DBusName
, method
, sig
);
400 var reply
= send_and_wait(call
);
403 reply
.check("IGNORED"); // we know it's an error
404 return default(WvAutoCast
);
407 return reply
.iter().pop();
410 public string GetUnixUserName(string name
)
412 return CallDBusMethod("GetConnectionUnixUserName", name
);
415 public ulong GetUnixUser(string name
)
417 return CallDBusMethod("GetConnectionUnixUser", name
);
420 public string GetCert(string name
)
422 return CallDBusMethod("GetCert", name
);
425 public string GetCertFingerprint(string name
)
427 return CallDBusMethod("GetCertFingerprint", name
);
430 public RequestNameReply
RequestName(string name
)
432 return RequestName(name
, NameFlag
.None
);
435 public RequestNameReply
RequestName(string name
, NameFlag flags
)
437 WvAutoCast reply
= CallDBusMethod("RequestName", name
, (uint)flags
);
438 return (RequestNameReply
)(uint)reply
;
441 public ReleaseNameReply
ReleaseName(string name
)
443 return (ReleaseNameReply
)(uint)CallDBusMethod("ReleaseName", name
);
446 public bool NameHasOwner(string name
)
448 return CallDBusMethod("NameHasOwner", name
);
451 public StartReply
StartServiceByName(string name
)
453 return StartServiceByName(name
, 0);
456 public StartReply
StartServiceByName(string name
, uint flags
)
458 var retval
= CallDBusMethod("StartServiceByName", name
, flags
);
459 return (StartReply
)(uint)retval
;
462 public void AddMatch(string rule
)
464 CallDBusMethod("AddMatch", rule
);
467 public void RemoveMatch(string rule
)
469 CallDBusMethod("RemoveMatch", rule
);
472 public string unique_name { get; private set; }
475 // These are here rather than in WvDbusMsg itself so that WvDbusMsg
476 // can be compiled entirely without knowing about connections.
477 public static class WvDbusHelpers
479 public static uint send(this WvDbusMsg msg
, WvDbus conn
)
481 return conn
.send(msg
);
484 public static uint send(this WvDbusMsg msg
, WvDbus conn
,
485 Action
<WvDbusMsg
> replyaction
)
487 return conn
.send(msg
, replyaction
);
490 public static WvDbusMsg
send_and_wait(this WvDbusMsg msg
,
493 return conn
.send_and_wait(msg
);