Make WvStreams compile with gcc 4.4.
[wvstreams.git] / dbus / wvdbusconn.cc
blob96c5fb1f54f94ee2349e63b99e7ee8c91d1a2af1
1 /* -*- Mode: C++ -*-
2 * Worldvisions Weaver Software:
3 * Copyright (C) 2004-2006 Net Integration Technologies, Inc.
5 * Pathfinder Software:
6 * Copyright (C) 2007, Carillon Information Security Inc.
8 * This library is licensed under the LGPL, please read LICENSE for details.
11 #include "wvdbusconn.h"
12 #include "wvmoniker.h"
13 #include "wvstrutils.h"
14 #undef interface // windows
15 #include <dbus/dbus.h>
18 static WvString translate(WvStringParm dbus_moniker)
20 WvStringList l;
21 WvStringList::Iter i(l);
23 if (!strncasecmp(dbus_moniker, "unix:", 5))
25 WvString path, tmpdir;
26 l.split(dbus_moniker+5, ",");
27 for (i.rewind(); i.next(); )
29 if (!strncasecmp(*i, "path=", 5))
30 path = *i + 5;
31 else if (!strncasecmp(*i, "abstract=", 9))
32 path = WvString("@%s", *i + 9);
33 else if (!strncasecmp(*i, "tmpdir=", 7))
34 tmpdir = *i + 7;
36 if (!!path)
37 return WvString("unix:%s", path);
38 else if (!!tmpdir)
39 return WvString("unix:%s/dbus.sock", tmpdir);
41 else if (!strncasecmp(dbus_moniker, "tcp:", 4))
43 WvString host, port, family;
44 l.split(dbus_moniker+4, ",");
45 for (i.rewind(); i.next(); )
47 if (!strncasecmp(*i, "family=", 7))
48 family = *i + 7;
49 else if (!strncasecmp(*i, "host=", 5))
50 host = *i + 5;
51 else if (!strncasecmp(*i, "port=", 5))
52 port = *i + 5;
54 if (!!host && !!port)
55 return WvString("tcp:%s:%s", host, port);
56 else if (!!host)
57 return WvString("tcp:%s", host);
58 else if (!!port)
59 return WvString("tcp:0.0.0.0:%s", port); // localhost
62 return dbus_moniker; // unrecognized
66 static IWvStream *stream_creator(WvStringParm _s, IObject *)
68 WvString s(_s);
70 if (!strcasecmp(s, "starter"))
72 WvString startbus(getenv("DBUS_STARTER_ADDRESS"));
73 if (!!startbus)
74 return IWvStream::create(translate(startbus));
75 else
77 WvString starttype(getenv("DBUS_STARTER_BUS_TYPE"));
78 if (!!starttype && !strcasecmp(starttype, "system"))
79 s = "system";
80 else if (!!starttype && !strcasecmp(starttype, "session"))
81 s = "session";
85 if (!strcasecmp(s, "system"))
87 // NOTE: the environment variable for the address of the system
88 // bus is very often not set-- in that case, look in your dbus
89 // system bus config file (e.g. /etc/dbus-1/system.conf) for the
90 // raw address and either set this environment variable to that, or
91 // pass in the address directly
92 WvString bus(getenv("DBUS_SYSTEM_BUS_ADDRESS"));
93 if (!!bus)
94 return IWvStream::create(translate(bus));
97 if (!strcasecmp(s, "session"))
99 WvString bus(getenv("DBUS_SESSION_BUS_ADDRESS"));
100 if (!!bus)
101 return IWvStream::create(translate(bus));
104 return IWvStream::create(translate(s));
107 static WvMoniker<IWvStream> reg("dbus", stream_creator);
110 static int conncount;
112 WvDBusConn::WvDBusConn(IWvStream *_cloned, IWvDBusAuth *_auth, bool _client)
113 : WvStreamClone(_cloned),
114 log(WvString("DBus %s%s",
115 _client ? "" : "s",
116 ++conncount), WvLog::Debug5),
117 pending(10)
119 init(_auth, _client);
123 WvDBusConn::WvDBusConn(WvStringParm moniker, IWvDBusAuth *_auth, bool _client)
124 : WvStreamClone(IWvStream::create(moniker)),
125 log(WvString("DBus %s%s",
126 _client ? "" : "s",
127 ++conncount), WvLog::Debug5),
128 pending(10)
130 log("Connecting to '%s'\n", moniker);
131 init(_auth, _client);
135 void WvDBusConn::init(IWvDBusAuth *_auth, bool _client)
137 log("Initializing.\n");
138 client = _client;
139 auth = _auth ? _auth : new WvDBusClientAuth;
140 authorized = in_post_select = false;
141 if (!client) set_uniquename(WvString(":%s.0", conncount));
143 if (!isok()) return;
145 delay_output(true);
147 // this will get enqueued until later, but we want to make sure it
148 // comes before anything the user tries to send - including anything
149 // goofy they enqueue in the authorization part.
150 if (client)
151 send_hello();
153 try_auth();
156 WvDBusConn::~WvDBusConn()
158 log("Shutting down.\n");
159 if (geterr())
160 log("Error was: %s\n", errstr());
162 close();
164 delete auth;
168 void WvDBusConn::close()
170 if (!closed)
171 log("Closing.\n");
172 WvStreamClone::close();
176 WvString WvDBusConn::uniquename() const
178 return _uniquename;
182 void WvDBusConn::request_name(WvStringParm name, const WvDBusCallback &onreply,
183 time_t msec_timeout)
185 uint32_t flags = (DBUS_NAME_FLAG_ALLOW_REPLACEMENT |
186 DBUS_NAME_FLAG_REPLACE_EXISTING);
187 WvDBusMsg msg("org.freedesktop.DBus", "/org/freedesktop/DBus",
188 "org.freedesktop.DBus", "RequestName");
189 msg.append(name).append(flags);
190 send(msg, onreply, msec_timeout);
194 uint32_t WvDBusConn::send(WvDBusMsg msg)
196 msg.marshal(out_queue);
197 if (authorized)
199 log(" >> %s\n", msg);
200 write(out_queue);
202 else
203 log(" .> %s\n", msg);
204 return msg.get_serial();
208 void WvDBusConn::send(WvDBusMsg msg, const WvDBusCallback &onreply,
209 time_t msec_timeout)
211 send(msg);
212 if (onreply)
213 add_pending(msg, onreply, msec_timeout);
217 class xxReplyWaiter
219 public:
220 WvDBusMsg *reply;
222 xxReplyWaiter()
223 { reply = NULL; }
224 ~xxReplyWaiter()
225 { delete reply; }
226 bool reply_wait(WvDBusMsg &msg)
227 { reply = new WvDBusMsg(msg); return true; }
231 WvDBusMsg WvDBusConn::send_and_wait(WvDBusMsg msg, time_t msec_timeout,
232 wv::function<void(uint32_t)> serial_cb)
234 xxReplyWaiter rw;
236 send(msg, wv::bind(&xxReplyWaiter::reply_wait, &rw, _1),
237 msec_timeout);
238 if (serial_cb)
239 serial_cb(msg.get_serial());
240 while (!rw.reply && isok())
241 runonce();
242 if (!rw.reply)
243 return WvDBusError(msg, DBUS_ERROR_FAILED,
244 WvString("Connection closed (%s) "
245 "while waiting for reply.",
246 errstr()));
247 else
248 return *rw.reply;
252 void WvDBusConn::out(WvStringParm s)
254 log(" >> %s", s);
255 print(s);
259 const char *WvDBusConn::in()
261 const char *s = trim_string(getline(0));
262 if (s)
263 log("<< %s\n", s);
264 return s;
268 void WvDBusConn::send_hello()
270 WvDBusMsg msg("org.freedesktop.DBus", "/org/freedesktop/DBus",
271 "org.freedesktop.DBus", "Hello");
272 send(msg, wv::bind(&WvDBusConn::_registered, this, _1));
273 WvDBusMsg msg2("org.freedesktop.DBus", "/org/freedesktop/DBus",
274 "org.freedesktop.DBus", "AddMatch");
275 msg2.append("type='signal'");
276 send(msg2); // don't need to monitor this for completion
280 void WvDBusConn::set_uniquename(WvStringParm s)
282 // we want to print the message before switching log.app, so that we
283 // can trace which log.app turned into which
284 log("Assigned name '%s'\n", s);
285 _uniquename = s;
286 log.app = WvString("DBus %s%s", client ? "" : "s", uniquename());
290 void WvDBusConn::try_auth()
292 bool done = auth->authorize(*this);
293 if (done)
295 // ready to send messages!
296 if (out_queue.used())
298 log(" >> (sending enqueued messages)\n");
299 write(out_queue);
302 authorized = true;
307 void WvDBusConn::add_callback(CallbackPri pri, WvDBusCallback cb, void *cookie)
309 callbacks.append(new CallbackInfo(pri, cb, cookie), true);
313 void WvDBusConn::del_callback(void *cookie)
315 // remember, there might be more than one callback with the same cookie.
316 CallbackInfoList::Iter i(callbacks);
317 for (i.rewind(); i.next(); )
318 if (i->cookie == cookie)
319 i.xunlink();
323 int WvDBusConn::priority_order(const CallbackInfo *a, const CallbackInfo *b)
325 return a->pri - b->pri;
328 bool WvDBusConn::filter_func(WvDBusMsg &msg)
330 log("<< %s\n", msg);
332 // handle replies
333 uint32_t rserial = msg.get_replyserial();
334 if (rserial)
336 Pending *p = pending[rserial];
337 if (p)
339 p->cb(msg);
340 pending.remove(p);
341 return true; // handled it
345 // handle all the generic filters
346 CallbackInfoList::Sorter i(callbacks, priority_order);
347 for (i.rewind(); i.next(); )
349 bool handled = i->cb(msg);
350 if (handled) return true;
353 return false; // couldn't handle the message, sorry
357 WvDBusClientAuth::WvDBusClientAuth()
359 sent_request = false;
363 wvuid_t WvDBusClientAuth::get_uid()
365 return wvgetuid();
369 bool WvDBusClientAuth::authorize(WvDBusConn &c)
371 if (!sent_request)
373 c.write("\0", 1);
374 WvString uid = get_uid();
375 c.out("AUTH EXTERNAL %s\r\n\0", WvHexEncoder().strflushstr(uid));
376 sent_request = true;
378 else
380 const char *line = c.in();
381 if (line)
383 if (!strncasecmp(line, "OK ", 3))
385 c.out("BEGIN\r\n");
386 return true;
388 else if (!strncasecmp(line, "ERROR ", 6))
389 c.seterr("Auth failed: %s", line);
390 else
391 c.seterr("Unknown AUTH response: '%s'", line);
395 return false;
399 time_t WvDBusConn::mintimeout_msec()
401 WvTime when = 0;
402 PendingDict::Iter i(pending);
403 for (i.rewind(); i.next(); )
405 if (!when || when > i->valid_until)
406 when = i->valid_until;
408 if (!when)
409 return -1;
410 else if (when <= wvstime())
411 return 0;
412 else
413 return msecdiff(when, wvstime());
417 bool WvDBusConn::post_select(SelectInfo &si)
419 bool ready = WvStreamClone::post_select(si);
420 if (si.inherit_request) return ready;
422 if (in_post_select) return false;
423 in_post_select = true;
425 if (!authorized && ready)
426 try_auth();
428 if (!alarm_remaining())
430 WvTime now = wvstime();
431 PendingDict::Iter i(pending);
432 for (i.rewind(); i.next(); )
434 if (now > i->valid_until)
436 log("Expiring %s\n", i->msg);
437 expire_pending(i.ptr());
438 i.rewind();
443 if (authorized && ready)
445 // put this in a loop so that wvdbusd can forward packets rapidly.
446 // Otherwise TCP_NODELAY kicks in, because we do a select() loop
447 // between packets, which causes delay_output() to flush.
448 bool ran;
451 ran = false;
452 size_t needed = WvDBusMsg::demarshal_bytes_needed(in_queue);
453 size_t amt = needed - in_queue.used();
454 if (amt < 4096)
455 amt = 4096;
456 read(in_queue, amt);
457 WvDBusMsg *m;
458 while ((m = WvDBusMsg::demarshal(in_queue)) != NULL)
460 ran = true;
461 filter_func(*m);
462 delete m;
464 } while (ran);
467 alarm(mintimeout_msec());
468 in_post_select = false;
469 return false;
473 bool WvDBusConn::isidle()
475 return !out_queue.used() && pending.isempty();
479 void WvDBusConn::expire_pending(Pending *p)
481 if (p)
483 WvDBusCallback xcb(p->cb);
484 pending.remove(p); // prevent accidental recursion
485 WvDBusError e(p->msg, DBUS_ERROR_FAILED,
486 "Timed out while waiting for reply");
487 xcb(e);
492 void WvDBusConn::cancel_pending(uint32_t serial)
494 Pending *p = pending[serial];
495 if (p)
497 WvDBusCallback xcb(p->cb);
498 WvDBusMsg msg(p->msg);
499 pending.remove(p); // prevent accidental recursion
500 WvDBusError e(msg, DBUS_ERROR_FAILED,
501 "Canceled while waiting for reply");
502 xcb(e);
507 void WvDBusConn::add_pending(WvDBusMsg &msg, WvDBusCallback cb,
508 time_t msec_timeout)
510 uint32_t serial = msg.get_serial();
511 assert(serial);
512 if (pending[serial])
513 cancel_pending(serial);
514 pending.add(new Pending(msg, cb, msec_timeout), true);
515 alarm(mintimeout_msec());
519 bool WvDBusConn::_registered(WvDBusMsg &msg)
521 WvDBusMsg::Iter i(msg);
522 _uniquename = i.getnext().get_str();
523 set_uniquename(_uniquename);
524 return true;