Apresentando conceitos e demonstrando na prática as aplicações do sistema de comunicação entre processos

O D-Bus é um sistema de IPC (Inter-Process Communication, ou comunicação entre processos) criado e mantido como parte do freedesktop.org, projeto dedicado à interoperabilidade e ao desenvolvimento de tecnologias compartilhadas entre os ambientes gráficos para o X Window System, incluindo os populares GNOME e KDE.

Criado para atender às necessidades desses dois últimos, o D-Bus é, hoje, utilizado por vários outros projetos, incluindo o servidor de áudio PulseAudio e a stack Bluetooth BlueZ. Além disso, os bindings existentes para as mais diversas linguagens de programação facilitam bastante a sua adoção.

A proposta deste texto é apresentar os conceitos básicos e demonstrar como um desenvolvedor pode fazer uso do sistema. Portanto, não se trata de uma abordagem minuciosa dos detalhes referentes ao seu funcionamento. Materiais recomendados para um maior aprofundamento no tema podem ser encontrados ao final do texto, nas seções “Indo Além” e “Referências”.

Conceitos

Muitos dos termos utilizados no âmbito da OOP (Object-Oriented Programming, ou programação orientada a objetos), como, por exemplo, “objeto” e “método”, são também aplicados ao D-Bus, muito embora para nomear conceitos distintos. Desta forma, é necessário atenção para não confundir os significados.

Bus

Podemos descrever um bus como um canal através do qual programas se comunicam. Geralmente, e a depender do ambiente analisado, há, por padrão, um system bus, único e destinado à troca de informações entre todo o sistema operacional, e um session bus para cada sessão de usuário ativa, onde os dados trafegados se restrigem a ela.

Cada bus possui um endereço para acesso, que frequentemente é o caminho de um Unix-domain socket (soquete de domínio Unix). No entanto, outros esquemas de comunicação podem ser utilizados. É possível, por exemplo, que um bus esteja associado a uma porta TCP, sendo, assim, acessível por meio do endereço dela.

Bus Name

Os bus names são nomes atribuídos às conexões dos programas a um bus. São divididos em dois tipos: unique connection names (nomes únicos de conexão) e well-known names (nomes conhecidos).

Os unique connection names são únicos e imutáveis, atribuídos a cada conexão no momento em que elas são estabelecidas e nunca reutilizados no mesmo bus ao longo de todo o tempo em que ele existir, mesmo que as conexões a que eles estão associados se encerrem. Se iniciam com dois pontos (“:”), uma característica exclusiva, que não pode ser imitada pelos well-known names. Exemplo: :1.72.

Os well-known names são nomes atribuídos às conexões por solicitação dos programas. Constituem-se de dois ou mais elementos separados por pontos (“.”). Por convenção, adotam o formato de nomes de domínio revertidos. Exemplo: com.radialle.app.

Deve-se ressaltar que o termo “bus namenão se refere ao “nome de um bus”, ainda que possa sugerir esta ideia.

Objeto

Uma das pontas de qualquer comunicação realizada através do D-Bus é um objeto, elemento de um programa que se torna acessível aos demais ao ser registrado em um bus.

Cada objeto é identificado por um object path (caminho de objeto) como, por exemplo, /com/radialle/app/example. Pelos padrões que seguem, os object paths se assemelham aos caminhos de sistemas de arquivos em estilo Unix. No entanto, ao contrário do que poderia-se pensar, eles não necessariamente indicam relações hierárquicas.

Os objetos existem dentro do contexto de uma conexão. Para acessá-los, é necessário conhecer o bus name ao qual o objeto desejado está associado, bem como o seu object path. Assim, um object path precisa ser único dentro de uma mesma conexão, embora possa se repetir no bus.

Em todo bus, há um objeto que representa o próprio bus. Ele é disponibilizado através conexão de bus name org.freedesktop.DBus e o seu object path é /. Dentre as suas funções, estão a de possibilitar a solicitação de well-known names e a de permitir que os programas conectados descubram as demais conexões ativas ao bus.

Proxy

Um proxy é uma representação de um objeto registrado em um bus que existe somente dentro de um programa, sendo de exclusiva responsabilidade do binding utilizado.

Em muitos bindings, o proxy tem a forma de um objeto nativo da linguagem de programação utilizada, permitindo que o desenvolvedor acesse um objeto do D-Bus de maneira muito similar a como faria com um objeto regular.

Método

Os métodos são associados a objetos, e representam operações de um programa que podem ser chamadas pelos demais processos conectados ao mesmo bus. Assim como as tradicionais funções das linguagens de programação, ou mesmo os métodos da OOP, eles podem requerer argumentos (input parameters, ou parâmetros de entrada) e retornar dados (output parameters, ou parâmetros de saída). Se ocorrer um erro durante a execução da operação, eles podem também retornar uma exceção contendo um nome e uma mensagem de erro.

Muitos detalhes costumam ser simplificados pelos bindings. É comum, por exemplo, que a chamada de um método através de um proxy seja efetuada com argumentos de entrada de tipos nativos da linguagem de programação utilizada, com o processo de conversão para os tipos suportados pelo D-Bus ficando sob responsabilidade da biblioteca. Similarmente, os parâmetros de saída e as exceções do D-Bus também podem ser convertidos para as suas contrapartes nativas, possibilitando o tratamento pelo desenvolvedor da maneira convencional.

Sinal

Sinais (signals) são um mecanismo através do qual um objeto pode enviar, a qualquer momento, informações aos programas conectados ao bus.

É importante notar, no entanto, que os sinais não são automaticamente recebidos por todos os programas conectados ao bus; eles precisam se registrar para recebê-los, especificando os sinais desejados. Há, portanto, a possibilidade de um sinal ser emitido sem que existam destinatários registrados e não ser recebido por qualquer conexão.

Interface

Uma interface especifica um conjunto de membros — ou seja, métodos e sinais — que pode ser implementado por um objeto. A implementação de uma interface é uma promessa de que o objeto conterá todos os membros definidos por ela.

Diversos objetos podem implementar uma mesma interface, bem como múltiplas interfaces podem ser implementadas por um único objeto. Se membros idênticos forem definidos por duas ou mais interfaces implementadas pelo objeto, um programa que tente acessar qualquer um deles utilizando somente o object path e o nome não terá qualquer garantia sobre qual será efetivamente acessado. Para evitar este problema, é importante que se especifique, também, a interface à qual pertence o membro desejado.

Propriedade

As propriedades são associadas aos objetos e dedicadas ao armazenamento de informações. Elas podem ser lidas ou modificadas, a depender das permissões que lhe foram atribuídas, pelos programas conectados ao bus.

A descoberta e o acesso às propriedades são realizados por meio de membros dos objetos. Uma leitura simples do valor contido em uma delas, por exemplo, seria efetuada através de uma chamada ao método Get da interface org.freedesktop.DBus.Properties, implementada pelo mesmo objeto ao qual a propriedade estivesse associada.

Prática

Esclarecidos os conceitos básicos relacionados ao D-Bus, devemos passar à prática para fixar o conhecimento e adquirir uma melhor compreensão das possibilidades que o sistema oferece.

Para os fins deste texto, usaremos exemplos escritos em Python 3.x, que utilizam o binding pydbus e foram testados em uma instalação do Debian 9 “Stretch”. Eles devem, no entanto, funcionar na maioria dos ambientes Linux, desde que sejam observados os requerimentos do pydbus — inclusive a necessidade da GLib 2.46 e do girepository 1.46 ou superiores para registrar objetos em um bus — , do interpretador Python e do próprio D-Bus.

pydbus

Em sua página do GitHub, o pydbus é definido como uma biblioteca “pythônica” para o D-Bus. Seu README é simples e direto, contendo diversos exemplos de uso básico do binding. No mesmo repositório há, ainda, um tutorial com informações mais detalhadas e outros scripts de demonstração. Muito embora essas leituras não sejam obrigatórias para a compreensão do restante deste texto, elas são, sem dúvida, recomendadas.

MPRIS

Para facilitar a integração de players de mídia com outros programas através do D-Bus, foi criada a MPRIS (Media Player Remote Interfacing Specification).

Ao definir convenções para a escolha de bus names, determinar os objetos que devem existir, seus paths, as interfaces que devem implementar, dentre outros padrões, a especificação simplifica o desenvolvimento de programas que fazem uso das informações e dos serviços oferecidos pelas aplicações dedicadas à reprodução de áudio e vídeo.

Como exemplo, podemos destacar que a MPRIS, desde a sua versão 2.0 até a 2.2 — a mais recente disponível no momento da elaboração deste texto — determina o uso de um unique connection name iniciado por org.mpris.MediaPlayer2., o que torna possível obter uma lista de players compatíveis conectados a um bus através da aquisição e filtragem de uma lista completa de bus names.

pdmtools

O pdmtools é um conjunto de duas ferramentas em Python criadas especificamente para acompanhar este texto, servindo como exemplo prático do uso do pydbus para interação com outros programas via D-Bus.

A primeira delas, chamada lsmpris, exibe uma lista contendo os nomes e os bus names dos players compatíveis com a MPRIS 2.x conectados ao bus especificado.

A segunda, nomeada fakeplayer, simula um player compatível ao solicitar um bus name iniciado por org.mpris.MediaPlayer2. e registrar um objeto com o path /org/mpris/MediaPlayer2 contendo a propriedade Identity. Obviamente, esta é a implementação de apenas um pequeno fragmento da MPRIS, útil somente para fins de estudo e para debugging da lsmpris.

Ferramentas do D-Bus

Os releases do D-Bus incluem ferramentas de linha de comando destinadas à interação com o sistema. Dentre elas, encontram-se a dbus-send, que possibilita o envio de mensagens aos objetos especificados, e a dbus-monitor, para monitoramento das informações trafegadas em um bus.

Vemos, a seguir, um exemplo de utilização da dbus-send. Nele, a ferramenta é usada para chamar o método Get da interface org.freedesktop.DBus.Properties, implementada pelo objeto /org/mpris/MediaPlayer2 associado à conexão que utiliza o bus name org.mpris.MediaPlayer2.fakeplayer. Especificam-se, como parâmetros de entrada, as strings “org.mpris.MediaPlayer2” e “Identity”, que correspondem, respectivamente, à interface e ao nome da propriedade cujo valor deseja-se obter.

dbus-send \
--print-reply \
--dest=org.mpris.MediaPlayer2.fakeplayer \
/org/mpris/MediaPlayer2 \
org.freedesktop.DBus.Properties.Get \
string:"org.mpris.MediaPlayer2" string:"Identity"

Uma das tarefas que podem ser cumpridas com a dbus-monitor é, conforme demonstrado a seguir, a observação dos sinais emitidos por uma determinada conexão. A ferramenta é chamada com um filtro como parâmetro, de maneira que as únicas mensagens trocadas através do bus a serem exibidas por ela sejam os sinais originados da conexão de bus nameorg.mpris.MediaPlayer2.vlc, normalmente associada a uma instância do VLC media player.

dbus-monitor \
"type='signal',sender='org.mpris.MediaPlayer2.vlc'"

Nos dois exemplos anteriores, não é especificado um bus ao qual as ferramentas devem se conectar. Para este caso, ambas adotam como padrão o uso do session bus.

D-Feet

Ainda que a possibilidade de interagir com o D-Bus diretamente pela linha de comando seja bastante útil, inclusive para automatização com shell scripts, não se trata de uma maneira fácil de explorar e compreender as conexões ativas a um bus, os objetos associados a elas, seus métodos, sinais e propriedades. Uma melhor alternativa é o D-Feet, um software com interface gráfica dedicado às tarefas de debugging relacionadas ao D-Bus e mantido como parte do projeto GNOME.

Por padrão, o D-Feet exibe, no topo de sua janela principal, as abas “System Bus” e “Session Bus”, que, como os nomes indicam, correspondem, respectivamente, ao system bus e ao session bus da sessão de usuário ativa. No canto superior direito, um botão hambúrguer dá acesso às opções de conectar a ferramenta a um outro bus, ou encerrar uma conexão existente.

Ao lado esquerdo da mesma janela, existe uma lista de bus names, correspondente à aba selecionada. Clicando sobre um dos itens, surge, do lado direito, uma lista com os objetos associados à conexão e todas as informações referentes a eles.

No exemplo acima, vemos o D-Feet exibir o único objeto criado pela fakeplayer, de object path /org/mpris/MediaPlayer2, que, por sua vez, define a propriedade Identity, com permissão somente de leitura. Foi efetuado um duplo clique sobre o seu nome para que o valor associado fosse exibido: “Fake Player”.

Nota-se que a aplicação simplifica o acesso às propriedades, invocando o método e exibindo os resultados de maneira transparente para o usuário. No entanto, como ela também permite chamar métodos, podemos usá-la para acionar o Get da interface org.freedesktop.DBus.Propertiesmanualmente, se assim desejarmos.

Uma das funções que não faz parte da ferramenta até a mais recente versão disponível durante a elaboração deste texto (0.3.11) é a de receber os sinais emitidos, muito embora ela permita visualizar quais deles são definidos pelas interfaces que os objetos implementam.

Indo Além

Há aspectos mais avançados do D-Bus que, apesar de não detalhados neste texto, merecem ser mencionados, uma vez que podem ser bastante úteis para aqueles que pretendem se aprofundar na utilização do sistema.

Ativação

Um programa com suporte à ativação via D-Bus não precisa permanecer em execução para receber as mensagens a ele direcionadas; o sistema se encarregará de chamá-lo quando for solicitado.

O desenvolvimento de um software compatível com esse recurso envolve a criação de uma service file, que fornece ao D-Bus informações como o caminho para o programa a ser executado e o bus name a ser utilizado.

Detalhes podem ser encontrados em documentos como o tutorial “Creating and Installing D-Bus Autostart Services”, publicado na KDE TechBase.

Segurança

Há diversos recursos para ajudar a garantir a segurança dos programas conectados e das informações trocadas por meio do D-Bus.

O sistema permite que se configure um bus para requerer autenticação, de forma que aplicações não possam se conectar a ele sem apresentar as credenciais adequadas. Também é possível aplicar uma security policy (política de segurança), o que permite a filtragem dos dados trafegados por características como origem, destinatário e conteúdo.

Normalmente, o system bus adota, por padrão, uma política de segurança bastante restritiva, impedindo, por exemplo, a solicitação de bus names e a chamada de qualquer método por processos que não tenham sido explicitamente autorizados.

Mais informações a respeito podem ser encontradas na documentação do dbus-daemon.

Referências