https://anadoxin.org/blog

D-Bus, UDisks and Glibmm bindings

Tue, 01 July 2014 :: #linux

One day I was having a problem with simple D-Bus concept. I was using Glibmm D-Bus bindings (Gio::DBus namespace) to access the UDisks interface. I wanted to read some attributes of every hard disk found in the system, so first I needed to enumerate all disks that UDisks was reporting, like this:

Glib::RefPtr<Gio::DBus::Connection> bus;

int main() {
    using namespace Glib;
    using namespace Gio;

    Glib::init();
    Gio::init();

    bus = DBus::Connection::get_sync(Gio::DBus::BUS_TYPE_SYSTEM);
    RefPtr<DBus::Proxy> udisks_proxy = DBus::Proxy::create_sync(bus, "org.freedesktop.UDisks", "/org/freedesktop/UDisks", "org.freedesktop.UDisks");

    VariantContainerBase devices_variant = udisks_proxy->call_sync("EnumerateDevices");
    VariantIter iterator(devices_variant.get_child(0));

    Variant<ustring> var;
    while(iterator.next_value(var)) {
        ustring name = var.get();

        LOG("device: '%s", name.c_str());
        process_device(name);
    }

    return 0;
}

This seemed to work OK, because call_sync() returned a VariantContainerBase, which holds the (ao) object, which basically is: one struct of an array of object paths. From the documentation I read that the object path type is handled in the same way as a string type, that's why the untyped VariantBase that is created during get_child(0) allows itself to be casted into Variant<ustring> object. Using this parametrized Variant, it's trivial to extract the string by using var.get().

But then I was trying to read some stuff (in that particular case, the NativePath attribute) from the attributes of each drive using this method:

void process_device(const Glib::ustring& objpath) {
	using namespace Glib;
	using namespace Gio;

	RefPtr<DBus::Proxy> attrs = DBus::Proxy::create_sync(bus, "org.freedesktop.UDisks", objpath, "org.freedesktop.DBus.Properties");

	std::vector<VariantBase> args;
	args.push_back(Variant<ustring>::create(objpath));
	args.push_back(Variant<ustring>::create("NativePath"));
	VariantContainerBase data = attrs->call_sync("Get", VariantContainerBase::create_tuple(args));

	LOG("return type: %s", data.get_type_string().c_str());
}

The problem was that the VariantContainerBase object contained the (v) signature. This means that the object is variant, so I couldn't cast it to anything.

Introspection of the attributes showed that NativePath was holding a string value. So why the call_sync() method was returning an object of a variant type? How the NativePath attribute could be read, without using get_data() method and without doing memcpy of the data into manually allocated buffer?

The solution would suggest that probably it was a bug in glibmm's bindings.

Instead of calling VariantContainerBase's get_child(0), a proper way is to call a direct glib function: g_variant_get_child(), like this:

GVariant *output;
g_variant_get_child(data.gobj(), 0, "v", & output);

It will properly set the output's variant type to one that's insive v (in my case it will set the type of output to s). After this, when I got the remote data in the s type, I could cast it to Variant<ustring> type like this:

Variant<Glib::ustring> item(output);
Glib::ustring text = item.get();

since, fortunately, Variant's constructor accepts the old, glib-ish GVariant * type.

I'm still not sure whether it was a bug, or a feature.