Allow schema files that are missing checksums on the !!SCHEMAMATIC line.
[versaplex.git] / wvdbus-sharp / conn.cs
blobae339d62d415b9c58855fa2cbbe4f9a38bf36a49
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
5 //
6 using System;
7 using System.Collections.Generic;
8 using System.IO;
9 using System.Reflection;
10 using Wv.Extensions;
12 namespace Wv
14 [Flags]
15 public enum NameFlag : uint
17 None = 0,
18 AllowReplacement = 0x1,
19 ReplaceExisting = 0x2,
20 DoNotQueue = 0x4,
23 public enum RequestNameReply : uint
25 PrimaryOwner = 1,
26 InQueue,
27 Exists,
28 AlreadyOwner,
31 public enum ReleaseNameReply : uint
33 Released = 1,
34 NonExistent,
35 NotOwner,
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 += () => {
59 handlemessages(0);
62 // write the credential byte (needed for passing unix uids over
63 // the unix domain socket, but anyway, part of the protocol
64 // spec).
65 stream.write(new byte[] { 0 });
67 if (stream.err != null)
68 throw stream.err;
70 // Run the authentication phase
71 var auth = new Dbus.ExternalAuthClient(this, stream);
72 auth.Run();
74 unique_name = CallDBusMethod("Hello");
77 public void Dispose()
79 using (stream)
81 stream = null;
85 static WvBufStream make_stream(string address)
87 WvStream s;
88 WvUrl url = address_to_url(address);
89 if (url.method == "unix")
91 if (url.path.ne())
92 s = new WvUnix(url.path);
93 else
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);
102 else
103 throw new Exception(wv.fmt("Unknown connection method {0}",
104 url.method));
105 return new WvBufStream(s);
108 uint serial = 0;
109 uint GenerateSerial()
111 return ++serial;
114 public WvDbusMsg send_and_wait(WvDbusMsg msg)
116 WvDbusMsg reply = null;
118 send(msg, (r) => { reply = r; });
119 stream.flush(-1);
121 while (reply == null && ok)
122 handlemessage(-1);
124 return reply;
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;
132 return msg.serial;
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,
140 msg.ifc, msg.method,
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)
153 throw new Exception(
154 wv.fmt("Message length {0} > max {1}",
155 len, Dbus.Protocol.MaxMessageLength));
157 printmsg(" >>", hdata, msg);
158 stream.write(hdata);
159 if (msg.Body != null && msg.Body.Length != 0)
160 stream.write(msg.Body);
162 return msg.serial;
165 WvBuf inbuf = new WvBuf();
167 int entrycount = 0;
168 void readbytes(int max, int msec_timeout)
170 entrycount++;
171 wv.assert(entrycount == 1);
173 log.print(WvLog.L.Debug5, "Reading: have {0} of needed {1}\n",
174 inbuf.used, max);
175 int needed = max - inbuf.used;
176 if (needed > 0)
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);
185 entrycount--;
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);
199 if (inbuf.used < 16)
200 continue;
202 int needed = WvDbusMsg.bytes_needed(inbuf.peek(16));
203 readbytes(needed, remain);
204 if (inbuf.used < needed)
205 continue;
207 var msg = new WvDbusMsg(inbuf.get(needed));
208 printmsg("<< ", msg.GetHeaderData(), msg);
209 return msg;
212 return null;
215 bool handlemessage(int msec_timeout)
217 var m = readmessage(msec_timeout);
218 if (m != null)
220 // use ToArray() here in case the list changes while
221 // we're iterating
222 foreach (var handler in handlers.ToArray())
223 if (handler(m))
224 return true;
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?!");
230 return true;
232 return false;
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)
245 if (msg == null)
246 return false;
248 if (msg.rserial.HasValue)
250 Action<WvDbusMsg> raction
251 = rserial_to_action.tryget(msg.rserial.Value);
252 if (raction != null)
254 raction(msg);
255 return true;
259 switch (msg.type)
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);
270 return true;
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);
280 send(r);
282 return true;
283 case Dbus.MType.Invalid:
284 default:
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
296 // Non-standard:
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;
307 int port = 0;
309 if (method == "wv")
310 path = parts[1];
311 else
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}'",
319 prop));
321 string name = propa[0];
322 string value = propa[1];
324 if (name == "path")
325 path = value;
326 else if (name == "abstract")
327 path = "@" + value;
328 else if (name == "guid")
329 pass = value;
330 else if (name == "host")
331 host = value;
332 else if (name == "port")
333 port = value.atoi();
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
346 get {
347 string addr = wv.getenv("DBUS_SYSTEM_BUS_ADDRESS");
349 if (addr.e())
350 addr = SYSTEM_BUS_ADDRESS;
352 return addr;
356 public static string session_bus_address
358 get {
359 return wv.getenv("DBUS_SESSION_BUS_ADDRESS");
363 // FIXME: might as well cache this
364 public static WvDbus session_bus {
365 get {
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();
380 w.Write(param);
382 return CallDBusMethod(method, "s", w.ToArray());
385 WvAutoCast CallDBusMethod(string method, string p1, uint p2)
387 WvDbusWriter w = new WvDbusWriter();
388 w.Write(p1);
389 w.Write(p2);
391 return CallDBusMethod(method, "su", w.ToArray());
394 WvAutoCast CallDBusMethod(string method, string sig,
395 byte[] body)
397 var call = new WvDbusCall(DBusName, DBusPath,
398 DBusName, method, sig);
399 call.Body = body;
400 var reply = send_and_wait(call);
401 if (reply.err.ne())
403 reply.check("IGNORED"); // we know it's an error
404 return default(WvAutoCast);
406 else
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,
491 WvDbus conn)
493 return conn.send_and_wait(msg);