Recent argp changes prevented vxodbc tests from building on win32.
[versaplex.git] / wvdbus-sharp / conn.cs
blob58e0efcfc758f6547540e37c5df7dbab93f1db03
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
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 // Run the authentication phase
68 var auth = new Dbus.ExternalAuthClient(this, stream);
69 auth.Run();
71 unique_name = CallDBusMethod("Hello");
74 static WvBufStream make_stream(string address)
76 WvStream s;
77 WvUrl url = address_to_url(address);
78 if (url.method == "unix")
80 if (url.path.ne())
81 s = new WvUnix(url.path);
82 else
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);
91 else
92 throw new Exception(wv.fmt("Unknown connection method {0}",
93 url.method));
94 return new WvBufStream(s);
97 uint serial = 0;
98 uint GenerateSerial()
100 return ++serial;
103 public WvDbusMsg send_and_wait(WvDbusMsg msg)
105 WvDbusMsg reply = null;
107 send(msg, (r) => { reply = r; });
108 stream.flush(-1);
110 while (reply == null && ok)
111 handlemessage(-1);
113 return reply;
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;
121 return msg.serial;
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,
129 msg.ifc, msg.method,
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)
142 throw new Exception(
143 wv.fmt("Message length {0} > max {1}",
144 len, Dbus.Protocol.MaxMessageLength));
146 printmsg(" >>", hdata, msg);
147 stream.write(hdata);
148 if (msg.Body != null && msg.Body.Length != 0)
149 stream.write(msg.Body);
151 return msg.serial;
154 WvBuf inbuf = new WvBuf();
156 int entrycount = 0;
157 void readbytes(int max, int msec_timeout)
159 entrycount++;
160 wv.assert(entrycount == 1);
162 log.print(WvLog.L.Debug5, "Reading: have {0} of needed {1}\n",
163 inbuf.used, max);
164 int needed = max - inbuf.used;
165 if (needed > 0)
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);
174 entrycount--;
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);
188 if (inbuf.used < 16)
189 continue;
191 int needed = WvDbusMsg.bytes_needed(inbuf.peek(16));
192 readbytes(needed, remain);
193 if (inbuf.used < needed)
194 continue;
196 var msg = new WvDbusMsg(inbuf.get(needed));
197 printmsg("<< ", msg.GetHeaderData(), msg);
198 return msg;
201 return null;
204 bool handlemessage(int msec_timeout)
206 var m = readmessage(msec_timeout);
207 if (m != null)
209 // use ToArray() here in case the list changes while
210 // we're iterating
211 foreach (var handler in handlers.ToArray())
212 if (handler(m))
213 return true;
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?!");
219 return true;
221 return false;
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)
234 if (msg == null)
235 return false;
237 if (msg.rserial.HasValue)
239 Action<WvDbusMsg> raction
240 = rserial_to_action.tryget(msg.rserial.Value);
241 if (raction != null)
243 raction(msg);
244 return true;
248 switch (msg.type)
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);
259 return true;
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);
269 send(r);
271 return true;
272 case Dbus.MType.Invalid:
273 default:
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
285 // Non-standard:
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;
296 int port = 0;
298 if (method == "wv")
299 path = parts[1];
300 else
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}'",
308 prop));
310 string name = propa[0];
311 string value = propa[1];
313 if (name == "path")
314 path = value;
315 else if (name == "abstract")
316 path = "@" + value;
317 else if (name == "guid")
318 pass = value;
319 else if (name == "host")
320 host = value;
321 else if (name == "port")
322 port = value.atoi();
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
335 get {
336 string addr = wv.getenv("DBUS_SYSTEM_BUS_ADDRESS");
338 if (addr.e())
339 addr = SYSTEM_BUS_ADDRESS;
341 return addr;
345 public static string session_bus_address
347 get {
348 return wv.getenv("DBUS_SESSION_BUS_ADDRESS");
352 // FIXME: might as well cache this
353 public static WvDbus session_bus {
354 get {
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();
369 w.Write(param);
371 return CallDBusMethod(method, "s", w.ToArray());
374 WvAutoCast CallDBusMethod(string method, string p1, uint p2)
376 WvDbusWriter w = new WvDbusWriter();
377 w.Write(p1);
378 w.Write(p2);
380 return CallDBusMethod(method, "su", w.ToArray());
383 WvAutoCast CallDBusMethod(string method, string sig,
384 byte[] body)
386 var call = new WvDbusCall(DBusName, DBusPath,
387 DBusName, method, sig);
388 call.Body = body;
389 var reply = send_and_wait(call);
390 if (reply.err.ne())
392 reply.check("IGNORED"); // we know it's an error
393 return default(WvAutoCast);
395 else
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,
480 WvDbus conn)
482 return conn.send_and_wait(msg);