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
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 // Run the authentication phase
68 var auth
= new Dbus
.ExternalAuthClient(this, stream
);
71 unique_name
= CallDBusMethod("Hello");
74 static WvBufStream
make_stream(string address
)
77 WvUrl url
= address_to_url(address
);
78 if (url
.method
== "unix")
81 s
= new WvUnix(url
.path
);
83 throw new Exception("No path specified for UNIX transport");
85 else if (url
.method
== "tcp")
87 string host
= url
.host
.or("127.0.0.1");
88 int port
= url
.port
.or(5555);
89 s
= new WvTcp(host
, (ushort)port
);
92 throw new Exception(wv
.fmt("Unknown connection method {0}",
94 return new WvBufStream(s
);
103 public WvDbusMsg
send_and_wait(WvDbusMsg msg
)
105 WvDbusMsg reply
= null;
107 send(msg
, (r
) => { reply = r; }
);
110 while (reply
== null && ok
)
116 public uint send(WvDbusMsg msg
, Action
<WvDbusMsg
> replyaction
)
118 msg
.ReplyExpected
= true;
119 msg
.serial
= send(msg
);
120 rserial_to_action
[msg
.serial
] = replyaction
;
124 void printmsg(string prefix
, WvBytes hdata
, WvDbusMsg msg
)
126 log
.print(WvLog
.L
.Debug3
,
127 "{0} {1}#{2} ->{3} '{4}'.'{5}' ({6} bytes)\n",
128 prefix
, msg
.type
, msg
.serial
, msg
.dest
,
130 msg
.Body
==null ? 0 : msg
.Body
.Length
);
131 log
.print(WvLog
.L
.Debug4
, "Header:\n{0}", wv
.hexdump(hdata
));
132 log
.print(WvLog
.L
.Debug5
, "Body:\n{0}", wv
.hexdump(msg
.Body
));
135 public uint send(WvDbusMsg msg
)
137 msg
.serial
= GenerateSerial();
138 var hdata
= msg
.GetHeaderData();
140 long len
= hdata
.Length
+ (msg
.Body
!= null ? msg
.Body
.Length
: 0);
141 if (len
> Dbus
.Protocol
.MaxMessageLength
)
143 wv
.fmt("Message length {0} > max {1}",
144 len
, Dbus
.Protocol
.MaxMessageLength
));
146 printmsg(" >>", hdata
, msg
);
148 if (msg
.Body
!= null && msg
.Body
.Length
!= 0)
149 stream
.write(msg
.Body
);
154 WvBuf inbuf
= new WvBuf();
157 void readbytes(int max
, int msec_timeout
)
160 wv
.assert(entrycount
== 1);
162 log
.print(WvLog
.L
.Debug5
, "Reading: have {0} of needed {1}\n",
164 int needed
= max
- inbuf
.used
;
167 if (stream
.wait(msec_timeout
, true, false))
169 WvBytes b
= inbuf
.alloc(needed
);
170 int got
= stream
.read(b
);
171 inbuf
.unalloc(needed
-got
);
178 * You shouldn't use this, as it bypasses normal message processing.
179 * Add a message handler instead.
181 * It exists because it's useful in our unit tests.
183 public WvDbusMsg
readmessage(int msec_timeout
)
185 foreach (int remain
in wv
.until(msec_timeout
))
187 readbytes(16, remain
);
191 int needed
= WvDbusMsg
.bytes_needed(inbuf
.peek(16));
192 readbytes(needed
, remain
);
193 if (inbuf
.used
< needed
)
196 var msg
= new WvDbusMsg(inbuf
.get(needed
));
197 printmsg("<< ", msg
.GetHeaderData(), msg
);
204 bool handlemessage(int msec_timeout
)
206 var m
= readmessage(msec_timeout
);
209 // use ToArray() here in case the list changes while
211 foreach (var handler
in handlers
.ToArray())
215 // if we get here, there was a message but nobody could
216 // handle it. That's weird because our default handler
217 // should handle *everything*.
218 wv
.assert(false, "No default message handler?!");
224 public void handlemessages(int msec_timeout
)
226 while (handlemessage(msec_timeout
) && ok
)
230 public List
<Func
<WvDbusMsg
,bool>> handlers { get; private set; }
232 bool default_handler(WvDbusMsg msg
)
237 if (msg
.rserial
.HasValue
)
239 Action
<WvDbusMsg
> raction
240 = rserial_to_action
.tryget(msg
.rserial
.Value
);
250 case Dbus
.MType
.Error
:
251 //TODO: better exception handling
252 string errMsg
= String
.Empty
;
253 if (msg
.signature
.StartsWith("s")) {
254 errMsg
= msg
.iter().pop();
256 Console
.Error
.WriteLine
257 ("Remote Error: Signature='" + msg
.signature
258 + "' " + msg
.err
+ ": " + errMsg
);
260 case Dbus
.MType
.Signal
:
261 case Dbus
.MType
.MethodCall
:
262 // nothing to do with these by default, so give an error
263 if (msg
.ReplyExpected
)
265 var r
= msg
.err_reply
266 ("org.freedesktop.DBus.Error.UnknownMethod",
267 "Unknown dbus method '{0}'.'{1}'",
268 msg
.ifc
, msg
.method
);
272 case Dbus
.MType
.Invalid
:
274 throw new Exception("Invalid message received: Dbus.MType='" + msg
.type
+ "'");
278 Dictionary
<uint,Action
<WvDbusMsg
>> rserial_to_action
279 = new Dictionary
<uint,Action
<WvDbusMsg
>>();
281 // Standard D-Bus monikers:
282 // unix:path=whatever,guid=whatever
283 // unix:abstract=whatever,guid=whatever
284 // tcp:host=whatever,port=whatever
286 // wv:wvstreams_moniker
287 internal static WvUrl
address_to_url(string s
)
289 string[] parts
= s
.Split(new char[] { ':' }
, 2);
291 if (parts
.Length
< 2)
292 throw new Exception(wv
.fmt("No colon found in '{0}'", s
));
294 string method
= parts
[0];
295 string user
= null, pass
= null, host
= null, path
= null;
302 foreach (string prop
in parts
[1].Split(new char[] { ',' }
, 2))
304 string[] propa
= prop
.Split(new char[] { '=' }
, 2);
306 if (propa
.Length
< 2)
307 throw new Exception(wv
.fmt("No '=' found in '{0}'",
310 string name
= propa
[0];
311 string value = propa
[1];
315 else if (name
== "abstract")
317 else if (name
== "guid")
319 else if (name
== "host")
321 else if (name
== "port")
323 // else ignore it silently; extra properties might be used
324 // in newer versions for backward compatibility
328 return new WvUrl(method
, user
, pass
, host
, port
, path
);
331 const string SYSTEM_BUS_ADDRESS
332 = "unix:path=/var/run/dbus/system_bus_socket";
333 public static string system_bus_address
336 string addr
= wv
.getenv("DBUS_SYSTEM_BUS_ADDRESS");
339 addr
= SYSTEM_BUS_ADDRESS
;
345 public static string session_bus_address
348 return wv
.getenv("DBUS_SESSION_BUS_ADDRESS");
352 // FIXME: might as well cache this
353 public static WvDbus session_bus
{
355 if (session_bus_address
.e())
356 throw new Exception("DBUS_SESSION_BUS_ADDRESS not set");
357 return new WvDbus(session_bus_address
);
361 WvAutoCast
CallDBusMethod(string method
)
363 return CallDBusMethod(method
, "", new byte[0]);
366 WvAutoCast
CallDBusMethod(string method
, string param
)
368 WvDbusWriter w
= new WvDbusWriter();
371 return CallDBusMethod(method
, "s", w
.ToArray());
374 WvAutoCast
CallDBusMethod(string method
, string p1
, uint p2
)
376 WvDbusWriter w
= new WvDbusWriter();
380 return CallDBusMethod(method
, "su", w
.ToArray());
383 WvAutoCast
CallDBusMethod(string method
, string sig
,
386 var call
= new WvDbusCall(DBusName
, DBusPath
,
387 DBusName
, method
, sig
);
389 var reply
= send_and_wait(call
);
392 reply
.check("IGNORED"); // we know it's an error
393 return default(WvAutoCast
);
396 return reply
.iter().pop();
399 public string GetUnixUserName(string name
)
401 return CallDBusMethod("GetConnectionUnixUserName", name
);
404 public ulong GetUnixUser(string name
)
406 return CallDBusMethod("GetConnectionUnixUser", name
);
409 public string GetCert(string name
)
411 return CallDBusMethod("GetCert", name
);
414 public string GetCertFingerprint(string name
)
416 return CallDBusMethod("GetCertFingerprint", name
);
419 public RequestNameReply
RequestName(string name
)
421 return RequestName(name
, NameFlag
.None
);
424 public RequestNameReply
RequestName(string name
, NameFlag flags
)
426 WvAutoCast reply
= CallDBusMethod("RequestName", name
, (uint)flags
);
427 return (RequestNameReply
)(uint)reply
;
430 public ReleaseNameReply
ReleaseName(string name
)
432 return (ReleaseNameReply
)(uint)CallDBusMethod("ReleaseName", name
);
435 public bool NameHasOwner(string name
)
437 return CallDBusMethod("NameHasOwner", name
);
440 public StartReply
StartServiceByName(string name
)
442 return StartServiceByName(name
, 0);
445 public StartReply
StartServiceByName(string name
, uint flags
)
447 var retval
= CallDBusMethod("StartServiceByName", name
, flags
);
448 return (StartReply
)(uint)retval
;
451 public void AddMatch(string rule
)
453 CallDBusMethod("AddMatch", rule
);
456 public void RemoveMatch(string rule
)
458 CallDBusMethod("RemoveMatch", rule
);
461 public string unique_name { get; private set; }
464 // These are here rather than in WvDbusMsg itself so that WvDbusMsg
465 // can be compiled entirely without knowing about connections.
466 public static class WvDbusHelpers
468 public static uint send(this WvDbusMsg msg
, WvDbus conn
)
470 return conn
.send(msg
);
473 public static uint send(this WvDbusMsg msg
, WvDbus conn
,
474 Action
<WvDbusMsg
> replyaction
)
476 return conn
.send(msg
, replyaction
);
479 public static WvDbusMsg
send_and_wait(this WvDbusMsg msg
,
482 return conn
.send_and_wait(msg
);