Monday, February 23, 2009

Driving the D-Bus with Ruby

Having looked at the D-Bus from the client perspective before, its now time to get behind the wheel.

In order to drive the D-Bus, you need to decide on a couple of things:

  • which bus to drive
    you can choose between the system(-wide) bus or the (per-)session bus
  • the service name
    this is the name other clients can find your service, the name has dotted notation, e.g. my.awesome.Service
  • the objects to publish
    each service can provide any number of objects, denoted a slash-separated path name, e.g. /my/awesome/Service/thing
  • the interface name
    methods offered by objects are grouped by interface name. Usually its used to flag object capabilities. Interface names have dotted notation, usually prefixed by the service name, e.g. my.awesome.Service.Greeting
  • the methods
    these are the exported functions of the objects. Methods, also called members in D-Bus speak, have input parameters and a return value.

Obtaining a drivers license

Allowance for driving the D-Bus is controlled by config files below /etc/dbus-1. /etc/dbus-1/system.conf controls the system bus, /etc/dbus-1/session.conf controls the session bus. Both load additional configuration files from sub-directories (/etc/dbus-1/system.d/ resp. /etc/dbus-1/session.d/)

These config files are xml-based busconfig snippets, defining policies on who's allowed to own a service who can use which interfaces of the service.

A typical service configuration looks like this

 <!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
   "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
 <busconfig>
   <policy user="root">
     <allow own="my.awesome.Service" />
     <allow send_destination="my.awesome.Service" />
   </policy>
   <policy context="default">
     <allow send_destination="my.awesome.Service"
            send_interface="my.awesome.Service.Greeting" />
     <!-- introspection is allowed -->
     <allow send_destination="my.awesome.Service"
            send_interface="org.freedesktop.DBus.Introspectable" />
   </policy>
 </busconfig>
It gives root the right to own the service my.awesome.Service and to use it as a client. Any other user can only use the my.awesome.Service.Greeting interface of my.awesome.Service and can introspect it.

Convention tells you to name this busconfig file after your service with a .conf extension, e.g my.awesome.Service.conf. Place it below /etc/dbus-1/session.d/ or /etc/dbus-1/system.d/, depending which bus you want to drive.

Coding a D-Bus service

Examples for creating a D-Bus service using C are scarce and scary. Esp. programming in C while avoiding Glib, things become really ugly.

As we will see, using the ruby-dbus extension to Ruby makes creating D-Bus services a breeze.

We start with creating a DBus::Object defining (dbus_)methods under a (dbus_)interface.

#!/usr/bin/env ruby

require 'dbus'

class Awesome < DBus::Object
  # Create an interface.
  dbus_interface "my.awesome.Service.Greeting" do
    dbus_method :hola, "in name:s" do |name|
      puts "Hola #{name}!" 
    end
    dbus_method :hello, "in name:s, out res:s" do |name|
      "hello #{name}!" 
    end
    dbus_method :ahoj, "in name:s, in title:s" do |name,title|
      puts "Ahoj #{title} #{name}!" 
    end
  end
end

The dbus_method call get the method name passed as a symbol, followed by its signature. The signature describes the input ("in") and output ("out") parameters by name (before the colon) and type (after the colon). The parameter names are just syntactic sugar, but the type is essential and enforced by D-Bus. Look at D-Bus type signatures to find out how to specify types.

ruby-dbus uses the method name and signature to automatically generate an XML representation of the interface needed for D-Bus introspection. This allows clients to find out about available interfaces and their methods at runtime.

The following do ... end block contains the method implementation. Every "in" parameter is passed as a value to the block in typical ruby fashion.

Next is connecting to the bus and obtaining the service. You can easily export multiple services if the bus configuration allows that.

# Choose the bus (could also be DBus::session_bus)
bus = DBus::system_bus
# Define the service name
service = bus.request_service("my.awesome.Service")

Then we create the object and export it through the service.

# Set the object path
obj = Awesome.new("/my/awesome/Service/thing")
# Export it!
service.export(obj)

Note that there is no requirement to create and export objects in advance. You can start with a simple object and method, creating additional objects as requested by the client. Hal is a typical example for such a service.

Finally we start the listener and wait for incoming requests.

# Now listen to incoming requests
main = DBus::Main.new
main << bus
main.run

main.run never returns, as you would expect from a service daemon. Either kill(1) it, code a timeout signal or implement a method calling exit.

Running the service

Running the D-Bus service is as easy as running any other Ruby program:

kkaempf> ruby my_service.rb
This, however, will get you a Exception `DBus::Error' at (eval):26 - org.freedesktop.DBus.Error.AccessDenied: Connection ":1.967" is not allowed to own the service "my.awesome.Service" due to security policies in the configuration file

Looking at our busconfig defined above immediately reveals the error. Only user root is allowed to own the service. So we better do a

kkaempf> sudo ruby my_service.rb
to get this running.

Now congrats for passing your drivers exam ! ;-)

Friday, February 13, 2009

Riding the D-Bus with Ruby

The last time I looked at D-Bus is a couple of years ago. What I saw back then was promising in technology but ugly in (C-)programming. D-Bus has come a long way 'til then.

And so have my programming skills with scripting languages, esp. with Ruby. The ruby-dbus project provides a nice and easy-to-use programming API, once one has mastered the lack of examples and the D-Bus nomenclature.

About D-Bus

There is plenty of information available on D-Bus. I personally found the Introduction to D-Bus most valuable from a developers point of view.

D-Bus originates from the freedesktop.org initiative and is hosted at www.freedesktop.org

Basically, D-Bus is a RPC (remote procedure call) mechanism, allowing different programs to talk to each other and provide services in a standardized way.

The transport used for talking is called a bus, meaning everyone can ride (connect to) it. Usually there are two independent buses available

  • System bus
    This is for system-wide services, like hardware information (usually provided by HAL)
  • Session bus
    This is for per-login services, like a Gnome Desktop session.
Talking is done in a client/server fashion. The server code connects to the bus offering a service to be used by the clients. In order to find each other, services are names in a dotted syntax. E.g. org.freedesktop.Hal is offered by the HAL daemon providing hardware information.

The nice thing about D-Bus is that it allows introspection. You can ask a service about its capabilities. And the D-Bus itself is a service, so you just need to know about org.freedesktop.DBus to find all other services.

Services provide objects. These are organized in a tree-like fashion and typically addressed using slash-separated paths, just like filenames. Iterating over the objects of org.freedesktop.Hal gives you all devices.

Objects then have members. Members are methods to call, providing the functionality of the service. To make things more complicatedstructured, members are grouped in interfaces. Interfaces are comparable to capabilities of an object. For example the Hal object /org/freedesktop/Hal/devices/storage_model_DVDRW_LH_20A1S has the org.freedesktop.Hal.Device.Storage.Removable interface with the bool CheckForMedia() method.

Putting it all together gives you this chain
Bus (System/Session)
   -> Service (e.g. org.freedesktop.Hal)
   -> Object (e.g. /org/freedesktop/Hal/devices/storage_model_DVDRW_LH_20A1S)
   -> Interface (e.g. org.freedesktop.Hal.Device.Storage.Removable)
   -> Member (e.g. bool CheckForMedia())

There's more to D-Bus, like signals and signatures and activation and ... follow the documentation links given above if you want to learn it all. The explanation up to now should be sufficient to understand the basics of the ruby-dbus interface, however.

Using ruby-dbus

Connecting to the bus in Ruby is as easy as
require 'dbus'

bus = DBus::SystemBus.instance
# resp. 'bus = DBus::SessionBus.instance'
DBus::SystemBus is a Singleton, hence the .instance instead of the usual .new for creating the object.

Now you can create a proxy object. Its named 'proxy' because the real object lives on the other side of the connection, in the service. The proxy object is in your application and proxy-ing calls via D-Bus to the service object. You can use this to find all services on the bus

require 'dbus'

bus = DBus::SystemBus.instance

bus.proxy.ListNames[0].each do |service|
  puts "Service: #{service}"
end
Given a known service, D-Bus introspection allows to find its objects, subnodes and interfaces
require 'dbus'

bus = DBus::SystemBus.instance

# Create the proxy object
proxy = bus.introspect "org.freedesktop.Hal", "/"

# proxy.bus gives you the bus
# proxy.path is the object path
# proxy.destination is the service name

# Print object interfaces
proxy.interfaces.each do |interface|
  puts "Object #{proxy.path} provides #{interface}"
end

# Print object subnodes
proxy.subnodes.each do |path|
  puts "-> #{proxy.path}/#{path}"
end
A specific interface of an object can be accessed by the [] operator. And the interface knows about the signature of its methods.
require 'dbus'

bus = DBus::SystemBus.instance
# create proxy for the 'computer' device
proxy = bus.introspect "org.freedesktop.Hal", "/org/freedesktop/Hal/devices/computer"

# Print object interfaces
proxy.interfaces.each do |interface|
  puts "Object #{proxy.path} provides #{interface}"
  proxy[interface].methods.each do |key,value|
    puts "  #{value.rets} #{key}( #{value.params} )"
  end
end
Due to the dynamic nature of Ruby, Object methods are directly accessible in normal Ruby conventions. One just has to select the right interface first.
require 'dbus'

bus = DBus::SystemBus.instance
proxy = bus.introspect "org.freedesktop.Hal", "/org/freedesktop/Hal/devices/computer"

iface = proxy["org.freedesktop.Hal.Device.CPUFreq"]
freq = iface.GetCPUFreqPerformance
gov = iface.GetCPUFreqGovernor
puts "Frequency #{freq}, Governor #{gov}"
Riding the D-Bus with Ruby is easy and fun !