535 lines
18 KiB
C++
535 lines
18 KiB
C++
/*
|
|
* Copyright (C) 2011 Intel Corporation
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) version 3.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301 USA
|
|
*/
|
|
|
|
#include "gdbus-cxx-bridge.h"
|
|
#include <stdio.h>
|
|
#include <syncevo/gsignond-pipe-stream.h>
|
|
#include <syncevo/GuardFD.h>
|
|
#include <syncevo/GLibSupport.h>
|
|
#include <syncevo/util.h>
|
|
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
|
|
SE_GOBJECT_TYPE(GSignondPipeStream)
|
|
|
|
void intrusive_ptr_add_ref(GDBusConnection *con) { g_object_ref(con); }
|
|
void intrusive_ptr_release(GDBusConnection *con) { g_object_unref(con); }
|
|
void intrusive_ptr_add_ref(GDBusMessage *msg) { g_object_ref(msg); }
|
|
void intrusive_ptr_release(GDBusMessage *msg) { g_object_unref(msg); }
|
|
|
|
namespace GDBusCXX {
|
|
|
|
using namespace SyncEvo;
|
|
|
|
MethodHandler::MethodMap MethodHandler::m_methodMap;
|
|
boost::function<void (void)> MethodHandler::m_callback;
|
|
|
|
void appendArgInfo(GPtrArray *pa, const std::string &type)
|
|
{
|
|
// Empty if not used in the current direction (in or out),
|
|
// ignore then.
|
|
if (type.empty()) {
|
|
// TODO: replace runtime check with compile-time check
|
|
// via type specialization... not terribly important, though.
|
|
return;
|
|
}
|
|
|
|
GDBusArgInfo *argInfo = g_new0(GDBusArgInfo, 1);
|
|
argInfo->signature = g_strdup(type.c_str());
|
|
argInfo->ref_count = 1;
|
|
g_ptr_array_add(pa, argInfo);
|
|
}
|
|
|
|
struct OwnNameAsyncData
|
|
{
|
|
enum State {
|
|
OWN_NAME_WAITING,
|
|
OWN_NAME_OBTAINED,
|
|
OWN_NAME_LOST
|
|
};
|
|
|
|
OwnNameAsyncData(const std::string &name,
|
|
const boost::function<void (bool)> &obtainedCB) :
|
|
m_name(name),
|
|
m_obtainedCB(obtainedCB),
|
|
m_state(OWN_NAME_WAITING)
|
|
{}
|
|
|
|
static void busNameAcquired(GDBusConnection *connection,
|
|
const gchar *name,
|
|
gpointer userData) throw ()
|
|
{
|
|
boost::shared_ptr<OwnNameAsyncData> *data = static_cast< boost::shared_ptr<OwnNameAsyncData> *>(userData);
|
|
(*data)->m_state = OWN_NAME_OBTAINED;
|
|
try {
|
|
g_debug("got D-Bus name %s", name);
|
|
if ((*data)->m_obtainedCB) {
|
|
(*data)->m_obtainedCB(true);
|
|
}
|
|
} catch (...) {
|
|
(*data)->m_state = OWN_NAME_LOST;
|
|
}
|
|
}
|
|
|
|
static void busNameLost(GDBusConnection *connection,
|
|
const gchar *name,
|
|
gpointer userData) throw ()
|
|
{
|
|
boost::shared_ptr<OwnNameAsyncData> *data = static_cast< boost::shared_ptr<OwnNameAsyncData> *>(userData);
|
|
(*data)->m_state = OWN_NAME_LOST;
|
|
try {
|
|
g_debug("lost %s %s",
|
|
connection ? "D-Bus connection for name" :
|
|
"D-Bus name",
|
|
name);
|
|
if ((*data)->m_obtainedCB) {
|
|
(*data)->m_obtainedCB(false);
|
|
}
|
|
} catch (...) {
|
|
}
|
|
}
|
|
|
|
static void freeData(gpointer userData) throw ()
|
|
{
|
|
delete static_cast< boost::shared_ptr<OwnNameAsyncData> *>(userData);
|
|
}
|
|
|
|
static boost::shared_ptr<OwnNameAsyncData> ownName(GDBusConnection *conn,
|
|
const std::string &name,
|
|
boost::function<void (bool)> obtainedCB =
|
|
boost::function<void (bool)>()) {
|
|
boost::shared_ptr<OwnNameAsyncData> data(new OwnNameAsyncData(name, obtainedCB));
|
|
g_bus_own_name_on_connection(conn,
|
|
data->m_name.c_str(),
|
|
G_BUS_NAME_OWNER_FLAGS_NONE,
|
|
OwnNameAsyncData::busNameAcquired,
|
|
OwnNameAsyncData::busNameLost,
|
|
new boost::shared_ptr<OwnNameAsyncData>(data),
|
|
OwnNameAsyncData::freeData);
|
|
return data;
|
|
}
|
|
|
|
const std::string m_name;
|
|
const boost::function<void (bool)> m_obtainedCB;
|
|
State m_state;
|
|
};
|
|
|
|
void DBusConnectionPtr::undelay() const
|
|
{
|
|
if (!m_name.empty()) {
|
|
g_debug("starting to acquire D-Bus name %s", m_name.c_str());
|
|
boost::shared_ptr<OwnNameAsyncData> data = OwnNameAsyncData::ownName(get(),
|
|
m_name);
|
|
while (data->m_state == OwnNameAsyncData::OWN_NAME_WAITING) {
|
|
g_main_context_iteration(NULL, true);
|
|
}
|
|
g_debug("done with acquisition of %s", m_name.c_str());
|
|
if (data->m_state == OwnNameAsyncData::OWN_NAME_LOST) {
|
|
throw std::runtime_error("could not obtain D-Bus name - already running?");
|
|
}
|
|
}
|
|
g_dbus_connection_start_message_processing(get());
|
|
}
|
|
|
|
void DBusConnectionPtr::ownNameAsync(const std::string &name,
|
|
const boost::function<void (bool)> &obtainedCB) const
|
|
{
|
|
OwnNameAsyncData::ownName(get(), name, obtainedCB);
|
|
}
|
|
|
|
DBusConnectionPtr dbus_get_bus_connection(const char *busType,
|
|
const char *name,
|
|
bool unshared,
|
|
DBusErrorCXX *err)
|
|
{
|
|
DBusConnectionPtr conn;
|
|
GError* error = NULL;
|
|
GBusType type =
|
|
boost::iequals(busType, "SESSION") ?
|
|
G_BUS_TYPE_SESSION :
|
|
G_BUS_TYPE_SYSTEM;
|
|
|
|
if (unshared) {
|
|
char *address = g_dbus_address_get_for_bus_sync(type,
|
|
NULL, &error);
|
|
if(address == NULL) {
|
|
if (err) {
|
|
err->set(error);
|
|
}
|
|
return NULL;
|
|
}
|
|
// Here we set up a private client connection using the chosen bus' address.
|
|
conn = DBusConnectionPtr(g_dbus_connection_new_for_address_sync(address,
|
|
(GDBusConnectionFlags)
|
|
(G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
|
|
G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION),
|
|
NULL, NULL, &error),
|
|
false);
|
|
g_free(address);
|
|
|
|
if(conn == NULL) {
|
|
if (err) {
|
|
err->set(error);
|
|
} else {
|
|
g_clear_error(&error);
|
|
}
|
|
return NULL;
|
|
}
|
|
} else {
|
|
// This returns a singleton, shared connection object.
|
|
conn = DBusConnectionPtr(g_bus_get_sync(type,
|
|
NULL, &error),
|
|
false);
|
|
if(conn == NULL) {
|
|
if (err) {
|
|
err->set(error);
|
|
} else {
|
|
g_clear_error(&error);
|
|
}
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (name) {
|
|
// Request name later in undelay(), after the caller
|
|
// had a chance to add objects.
|
|
conn.addName(name);
|
|
// Acting as client, need to stop when D-Bus daemon dies.
|
|
g_dbus_connection_set_exit_on_close(conn.get(), TRUE);
|
|
}
|
|
|
|
return conn;
|
|
}
|
|
|
|
DBusConnectionPtr dbus_get_bus_connection(const std::string &address,
|
|
DBusErrorCXX *err)
|
|
{
|
|
// "address" needs to be the file descriptor number set up
|
|
// by DBusServerCXX::listen().
|
|
GuardFD fd(atoi(address.c_str()));
|
|
GSignondPipeStreamCXX stream(gsignond_pipe_stream_new(fd, fd, true),
|
|
TRANSFER_REF);
|
|
fd.release();
|
|
GErrorCXX gerror;
|
|
GDBusCXX::DBusConnectionPtr
|
|
conn(g_dbus_connection_new_sync(G_IO_STREAM(stream.get()),
|
|
NULL,
|
|
G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING,
|
|
NULL,
|
|
NULL,
|
|
gerror));
|
|
if (!conn && err) {
|
|
err->set(gerror.release());
|
|
}
|
|
return conn;
|
|
}
|
|
|
|
static void ConnectionLost(GDBusConnection *connection,
|
|
gboolean remotePeerVanished,
|
|
GError *error,
|
|
gpointer data)
|
|
{
|
|
DBusConnectionPtr::Disconnect_t *cb = static_cast<DBusConnectionPtr::Disconnect_t *>(data);
|
|
(*cb)();
|
|
}
|
|
|
|
static void DestroyDisconnect(gpointer data,
|
|
GClosure *closure)
|
|
{
|
|
DBusConnectionPtr::Disconnect_t *cb = static_cast<DBusConnectionPtr::Disconnect_t *>(data);
|
|
delete cb;
|
|
}
|
|
|
|
void DBusConnectionPtr::flush()
|
|
{
|
|
// ignore errors
|
|
g_dbus_connection_flush_sync(get(), NULL, NULL);
|
|
}
|
|
|
|
void DBusConnectionPtr::setDisconnect(const Disconnect_t &func)
|
|
{
|
|
g_signal_connect_closure(get(),
|
|
"closed",
|
|
g_cclosure_new(G_CALLBACK(ConnectionLost),
|
|
new Disconnect_t(func),
|
|
DestroyDisconnect),
|
|
true);
|
|
}
|
|
|
|
boost::shared_ptr<DBusServerCXX> DBusServerCXX::listen(const NewConnection_t &newConnection, DBusErrorCXX *)
|
|
{
|
|
// Create two fds connected via a two-way stream. The parent
|
|
// keeps fd[0] which gets closed automatically when the child
|
|
// execs. The parent closes the child's fd[1] after forking.
|
|
int fds[2];
|
|
int retval = socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, fds);
|
|
if (retval) {
|
|
SE_THROW(StringPrintf("socketpair: %s", strerror(errno)));
|
|
}
|
|
GuardFD parentfd(fds[0]);
|
|
GuardFD childfd(fds[1]);
|
|
|
|
// Child must inherit its fd.
|
|
int fdflags;
|
|
if ((fdflags = fcntl(childfd, F_GETFD)) == -1 ||
|
|
fcntl(childfd, F_SETFD, fdflags & ~FD_CLOEXEC)) {
|
|
SE_THROW(StringPrintf("fcntl: %s", strerror(errno)));
|
|
}
|
|
|
|
// Out listen "address" is the FD number.
|
|
std::string address = StringPrintf("%d", childfd.get());
|
|
|
|
// Transfer ownership of parent fd.
|
|
GSignondPipeStreamCXX stream(gsignond_pipe_stream_new(parentfd, parentfd, true),
|
|
TRANSFER_REF);
|
|
parentfd.release();
|
|
|
|
GErrorCXX gerror;
|
|
GDBusCXX::DBusConnectionPtr
|
|
connection(g_dbus_connection_new_sync(G_IO_STREAM(stream.get()),
|
|
NULL,
|
|
G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING,
|
|
NULL,
|
|
NULL,
|
|
gerror),
|
|
false);
|
|
if (!connection) {
|
|
gerror.throwError(SE_HERE, "creating GIO D-Bus connection");
|
|
}
|
|
|
|
// A fake DBusServerCXX which does nothing more than return the address, aka
|
|
// our FD number, and store data for the idle callback.
|
|
boost::shared_ptr<DBusServerCXX> res(new DBusServerCXX(address));
|
|
res->m_newConnection = newConnection;
|
|
res->m_connection = connection;
|
|
// Will be freed in the idle callback. Caller must have forked by then.
|
|
res->m_childfd = childfd.release();
|
|
|
|
// The caller must have some time to set up connection handling and fork.
|
|
// Delay the newConnection() callback until we enter the main event loop
|
|
// again. Callback must be removed when destructing prematurely because it
|
|
// has a plain "this" pointer.
|
|
res->m_connectionIdle = g_idle_add(onIdleOnce, &*res);
|
|
|
|
return res;
|
|
}
|
|
|
|
DBusServerCXX::DBusServerCXX(const std::string &address) :
|
|
m_connectionIdle(0),
|
|
m_childfd(-1),
|
|
m_address(address)
|
|
{
|
|
}
|
|
|
|
DBusServerCXX::~DBusServerCXX()
|
|
{
|
|
if (m_connectionIdle) {
|
|
g_source_remove(m_connectionIdle);
|
|
}
|
|
if (m_childfd >= 0) {
|
|
close(m_childfd);
|
|
}
|
|
}
|
|
|
|
gboolean DBusServerCXX::onIdleOnce(gpointer custom)
|
|
{
|
|
DBusServerCXX *me = static_cast<DBusServerCXX *>(custom);
|
|
me->m_connectionIdle = 0;
|
|
me->m_newConnection(*me, me->m_connection);
|
|
me->m_connection.reset();
|
|
close(me->m_childfd);
|
|
me->m_childfd = -1;
|
|
|
|
// not again
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
void Watch::nameOwnerChanged(GDBusConnection *connection,
|
|
const gchar *sender_name,
|
|
const gchar *object_path,
|
|
const gchar *interface_name,
|
|
const gchar *signal_name,
|
|
GVariant *parameters,
|
|
gpointer user_data)
|
|
{
|
|
Watch *watch = static_cast<Watch *>(user_data);
|
|
if (!watch->m_called) {
|
|
gchar *name = NULL, *oldOwner = NULL, *newOwner = NULL;
|
|
g_variant_get(parameters, "(sss)", &name, &oldOwner, &newOwner);
|
|
bool matches = name && watch->m_peer == name &&
|
|
newOwner && !*newOwner;
|
|
g_free(name);
|
|
g_free(oldOwner);
|
|
g_free(newOwner);
|
|
if (matches) {
|
|
watch->disconnected();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Watch::disconnected()
|
|
{
|
|
if (!m_called) {
|
|
m_called = true;
|
|
if (m_callback) {
|
|
m_callback();
|
|
}
|
|
}
|
|
}
|
|
|
|
Watch::Watch(const DBusConnectionPtr &conn,
|
|
const boost::function<void (void)> &callback) :
|
|
m_conn(conn),
|
|
m_callback(callback),
|
|
m_called(false),
|
|
m_watchID(0)
|
|
{
|
|
}
|
|
|
|
void Watch::setCallback(const boost::function<void (void)> &callback)
|
|
{
|
|
m_callback = callback;
|
|
if (m_called && m_callback) {
|
|
m_callback();
|
|
}
|
|
}
|
|
|
|
void Watch::activate(const char *peer)
|
|
{
|
|
if (!peer) {
|
|
throw std::runtime_error("Watch::activate(): no peer");
|
|
}
|
|
m_peer = peer;
|
|
|
|
// Install watch first ...
|
|
m_watchID = g_dbus_connection_signal_subscribe(m_conn.get(),
|
|
NULL, // TODO org.freedesktop.DBus?
|
|
"org.freedesktop.DBus",
|
|
"NameOwnerChanged",
|
|
"/org/freedesktop/DBus",
|
|
NULL,
|
|
G_DBUS_SIGNAL_FLAGS_NONE,
|
|
nameOwnerChanged,
|
|
this,
|
|
NULL);
|
|
if (!m_watchID) {
|
|
throw std::runtime_error("g_dbus_connection_signal_subscribe(): NameLost failed");
|
|
}
|
|
|
|
// ... then check that the peer really exists,
|
|
// otherwise we'll never notice the disconnect.
|
|
// If it disconnects while we are doing this,
|
|
// then disconnect() will be called twice,
|
|
// but it handles that.
|
|
GError *error = NULL;
|
|
|
|
GVariant *result = g_dbus_connection_call_sync(m_conn.get(),
|
|
"org.freedesktop.DBus",
|
|
"/org/freedesktop/DBus",
|
|
"org.freedesktop.DBus",
|
|
"NameHasOwner",
|
|
g_variant_new("(s)", peer),
|
|
G_VARIANT_TYPE("(b)"),
|
|
G_DBUS_CALL_FLAGS_NONE,
|
|
-1, // default timeout
|
|
NULL,
|
|
&error);
|
|
|
|
if (result != NULL) {
|
|
bool actual_result = false;
|
|
|
|
g_variant_get(result, "(b)", &actual_result);
|
|
if (!actual_result) {
|
|
disconnected();
|
|
}
|
|
} else {
|
|
std::string error_message(error->message);
|
|
g_error_free(error);
|
|
std::string err_msg("g_dbus_connection_call_sync(): NameHasOwner - ");
|
|
throw std::runtime_error(err_msg + error_message);
|
|
}
|
|
}
|
|
|
|
Watch::~Watch()
|
|
{
|
|
if (m_watchID) {
|
|
g_dbus_connection_signal_unsubscribe(m_conn.get(), m_watchID);
|
|
m_watchID = 0;
|
|
}
|
|
}
|
|
|
|
void getWatch(ExtractArgs &context,
|
|
boost::shared_ptr<Watch> &value)
|
|
{
|
|
std::unique_ptr<Watch> watch(new Watch(context.m_conn));
|
|
watch->activate((context.m_msg && *context.m_msg) ?
|
|
g_dbus_message_get_sender(*context.m_msg) :
|
|
context.m_sender);
|
|
value.reset(watch.release());
|
|
}
|
|
|
|
void ExtractArgs::init(GDBusConnection *conn,
|
|
GDBusMessage **msg,
|
|
GVariant *msgBody,
|
|
const char *sender,
|
|
const char *path,
|
|
const char *interface,
|
|
const char *signal)
|
|
{
|
|
m_conn = conn;
|
|
m_msg = msg;
|
|
m_sender = sender;
|
|
m_path = path;
|
|
m_interface = interface;
|
|
m_signal = signal;
|
|
if (msgBody != NULL) {
|
|
g_variant_iter_init(&m_iter, msgBody);
|
|
}
|
|
}
|
|
|
|
|
|
ExtractArgs::ExtractArgs(GDBusConnection *conn, GDBusMessage *&msg)
|
|
{
|
|
init(conn, &msg, g_dbus_message_get_body(msg), NULL, NULL, NULL, NULL);
|
|
}
|
|
|
|
ExtractArgs::ExtractArgs(GDBusConnection *conn,
|
|
const char *sender,
|
|
const char *path,
|
|
const char *interface,
|
|
const char *signal)
|
|
{
|
|
init(conn, NULL, NULL, sender, path, interface, signal);
|
|
}
|
|
|
|
ExtractResponse::ExtractResponse(GDBusConnection *conn, GDBusMessage *msg)
|
|
{
|
|
init(conn, NULL, g_dbus_message_get_body(msg),
|
|
g_dbus_message_get_sender(msg), NULL, NULL, NULL);
|
|
}
|
|
|
|
} // namespace GDBusCXX
|