HAL
В этой статье мы рассмотрим способы общения с системным демоном HAL.
Введение
HAL (Hardware Abstraction Layer) — это демон, хранящий информацию о системных устройствах, их типах, возможностях и другую информацию, которую он в состоянии получить через интерфейсы ядра. Демон предоставляет более высокоуровневый доступ к информации об устройствах, нежели прямое общение с sysfs и др. Используя HAL API мы может просканировать список системных устройств, найти в нём дисковые накопители, Audio CD, USB флеш карты, WiFi карты, клавиатуры, мыши и т. д. HAL также содержит несколько полезных сигналов, основываясь на которых мы можем отследить момент появления новых устройств в системе (например, когда был вставлен новый USB флеш накопитель, DVD диск, поднят WiFi интерфейс и т. д.). HAL предоставляет также и некоторые полезные методы, которые можно вызывать удалённо через интерфейс D-Bus.
Структура данных, в которой HAL хранит всю информацию, можно представить как ассоциативный массив. Ключом в этом массиве будет являться т. н. уникальный идентификатор устройства, или UDI. Значением этого ключа будет ещё один ассоциативный массив, в котором в свою очередь ключом будет имя свойства данного устройства, а значением — значение этого свойства для данного устройства. Имена UDI зависят от системы, не стоит полагаться на них постоянство. Чтобы было понятней, представим такую упрощённую структуру для некоего устройства:
UDI1 |---block.device /dev/sda1 |---block.is_volume 1 |---info.capabilities volume,block |---volume.label WORK `---volume.uuid 0717-AD77 UDI2 … UDI3 …
Здесь у некоего устройства с UDI, равным абстрактному UDI1, имеется 5 свойств. Имя каждого свойства строго определено. Значение свойства зависит от имени. Для устройства UDI1 мы может сказать следущее:
- на основании block.device — что этому устройству сопоставлен файл /dev/sda1 в файловой системе /dev;
- на основании block.is_volume — что это некий раздел;
- на основании info.capabilities — что это раздел на блочном устройстве хранения данных;
- на основании volume.label — что этот раздел имеет метку WORK;
- на основании volume.uuid — что этот раздел имеет UUID 0717-AD77.
Мы можем проверить эту информацию с помощью системной утилиты blkid (запускается от пользователя root):
# blkid | grep WORK /dev/sda1: LABEL="WORK" UUID="0717-AD77" TYPE="vfat"
Общение с HAL демоном происходит через D-Bus. Для D-Bus имеется множество биндингов для различных языков. Это значит, что для общения с HAL у нас не должно возникнуть трудностей. В этой статье мы постараемся рассказать о нескольких способах взаимодействия с HAL.
Также отметим, что на шине D-Bus демон предоставляет интерфейсы и методы для прямого вызова любым D-Bus клиентом. Например, с помощью qdbusviewer или любым консольным клиентом (qdbus, dbus-send).
Каждое устройство в иерархии может иметь дочерние устройства (например, как шина USB и подключённые к ней устройства).
Подготовка
Во всех современных дистрибутивах демон HAL уже должен быть установлен, настроен и запущен. Для программирования нам понадобятся только заголовки HAL и D-Bus. Для справки по HAL понадобится пакет hal-doc. В Debian-подобных системах их можно установить так:
# aptitude install libhal-dev libhal-storage-dev hal-doc
Теперь мы готовы для общения с HAL.
Для просмотра всех устройств и их свойств в графическом виде можно использовать утилиту hal-device-manager. Она присутствует в Debian Etch, но в более поздних дистрибутивах (в т.ч. и последних Ubuntu) следует использовать gnome-device-manager.
Свойства
В качестве примера рассмотрим некоторые имена свойств.
- info.capabilities — список строк, описание того, что из себя представляет это устройство. Это важное свойство для отделения устройств друг от друга по типу;
- info.parent — содержит UDI родительского элемента в иерархии HAL;
- block.device — содержит файл устройства в системной иерархии (например, /dev/sda1);
- block.is_volume — содержит значение true, если данное устройство — раздел и может быть смонтировано;
- volume.fstype — тип файловой системы раздела (если это раздел);
- volume.label — метка тома;
- volume.fsusage — как используется файловая система (обычная файловая система, RAID, неформатированная область и т.д.);
- linux.sysfs_path — путь к файлу устройства в иерархии sysfs.
За полным списком возможных имён свойств обратитесь к документации HAL.
Консоль
Мы можем общаться с HAL с помощью любого клиента D-Bus. Например, с помощью консольного dbus-send. Для каждого устройства на шине D-Bus предусмотрен интерфейс org.freedesktop.Hal.Device, который предоставляет методы для получения и установки свойств данного конкретного устройства.
Например, получим список облуживаемых устройств (похожий результат даст команда lshal):
$ dbus-send --system --print-reply --dest=org.freedesktop.Hal \ /org/freedesktop/Hal/Manager \ org.freedesktop.Hal.Manager.GetAllDevices method return sender=:1.1 -> dest=:1.28 reply_serial=2 array [ string "/org/freedesktop/Hal/devices/acpi_P001" string "/org/freedesktop/Hal/devices/acpi_P002" ... string "/org/freedesktop/Hal/devices/pci_10de_368" string "/org/freedesktop/Hal/devices/pci_10de_362" string "/org/freedesktop/Hal/devices/pci_10de_369" ]
Каждая строка в ответе — это UDI обслуживаемого устройства.
Теперь получим свойство info.capabilities для одного из дисковых разделов, основываясь на его UDI:
$ dbus-send --system --print-reply --dest=org.freedesktop.Hal \ /org/freedesktop/Hal/devices/volume_uuid_0717_AD77 \ org.freedesktop.Hal.Device.GetProperty \ string:info.capabilities method return sender=:1.1 -> dest=:1.31 reply_serial=2 array [ string "volume" string "block" ]
Теперь получим свойство volume.label (метка DOS) для этого же раздела:
$ dbus-send --system --print-reply --dest=org.freedesktop.Hal \ /org/freedesktop/Hal/devices/volume_uuid_0717_AD77 \ org.freedesktop.Hal.Device.GetProperty \ string:volume.label method return sender=:1.1 -> dest=:1.33 reply_serial=2 string "WORK"
Получим свойство volume.size (размер раздела в байтах) для этого же раздела:
$ dbus-send --system --print-reply --dest=org.freedesktop.Hal \ /org/freedesktop/Hal/devices/volume_uuid_0717_AD77 \ org.freedesktop.Hal.Device.GetProperty \ string:volume.size method return sender=:1.1 -> dest=:1.36 reply_serial=2 uint64 15002878464
C/C++
В этом разделе мы напишем HAL клиента на оригинальной библиотеке libhal (на языке C, стандарта C99). Это необходимо прежде всего для тулкитов, где нет высокоуровнего D-Bus клиента, либо для чисто консольной программы, которая не использует тулкиты вообще.
Итак, всё что нам понадобится — это один исходный файл и элементарный скрипт компиляции. Makefile можете написать по своему усмотрению.
Скрипт компиляции (назовём его mk):
<syntaxhighlight lang="sh">
- !/bin/sh
- компилируем исходник main.c. Не забываем о флагах и библиотеках HAL и D-Bus
gcc -o hal -O2 -std=c99 \
`pkg-config --cflags hal` `pkg-config --cflags dbus-1` \ main.c \ `pkg-config --libs hal` `pkg-config --libs dbus-1`
</syntaxhighlight>
Стратегия написания простейшего клиента HAL сводится к следующему:
- создать новый HAL контекст, используемый в HAL API;
- создать соединение с D-Bus сессией;
- ассоциировать HAL контекст с D-Bus соединением;
- инициализировать HAL;
- использовать функции из HAL API для общения с демоном (например, получим список всех устройств);
- деинициализировать клиента HAL.
<syntaxhighlight lang="c">
- include <stdio.h>
- include <stdlib.h>
- include <libhal.h>
- include <dbus/dbus.h>
// функция обработки UDI static void handleDevice(LibHalContext *ctx, const char *udi, DBusError *error) {
if(!udi) return;
printf("%s\n", udi);
// нас интересуют только устройства, имеющие свойство info.capabilities if(!libhal_device_property_exists(ctx, udi, "info.capabilities", NULL)) return;
// получим значение свойства (массив строк) int i = 0; char **capabilities = libhal_device_get_property_strlist(ctx, udi, "info.capabilities", NULL);
if(!capabilities) return;
// сейчас 'capabilities' представляет собой массив строк
// пройдёмся по всем строкам, выведем на экран значения while(*(capabilities+i)) { printf("\t%s\n", *(capabilities+i)); ++i; }
printf("\n");
// освободим память! libhal_free_string_array(capabilities);
}
static void die(const char *s) {
if(s) fprintf(stderr, "%s\n", s);
exit(1);
}
int main(int argc, char **argv) {
DBusConnection *dbus; LibHalContext *ctx; DBusError error;
// создаём новый контекст ctx = libhal_ctx_new();
if(!ctx) die("Cannot initialize HAL context");
// инициализируем объект error, он нам понадобится в функциях HAL API dbus_error_init(&error);
// создаём соединение с D-Bus dbus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
// ассоциируем D-Bus соединение с HAL контекстом libhal_ctx_set_dbus_connection(ctx, dbus);
// инициализируем HAL клиента с нашей стороны if(!libhal_ctx_init(ctx, &error)) die("Cannot initialize HAL");
// с помошью HAL API получаем список устройств, обслуживаемых демоном, // каждое устройство представляет собой строку с UDI int numDevices; char **halDevices = libhal_get_all_devices(ctx, &numDevices, NULL);
// нет устройств?? if(!halDevices) die("Cannot get device list");
printf("Number of devices: %d\n", numDevices);
// для каждого UDI вызываем функцию обработки for(int i = 0;i < numDevices;i++) handleDevice(ctx, halDevices[i], &error);
// освобождаем память! libhal_free_string_array(halDevices);
if(dbus_error_is_set(&error)) dbus_error_free(&error);
// завершаем D-Bus соединение dbus_connection_close(dbus); dbus_connection_unref(dbus);
// завершаем клиента libhal_ctx_free(ctx);
return 0;
} </syntaxhighlight>
Эта программа выдаст на консоль список всех обслуживаемых устройств, и свойство info.capabilities каждого устройства.
Qt
С помощью Qt общаться с HAL можно без особых трудностей, т.к. в 4-ю версию этого тулкита уже встроен D-Bus клиент (необходимы установленные пакеты libqt4-dbus и libqt4-dev).
Стратегия написания простейшего клиента HAL на Qt очень проста: с помощью D-Bus-клиента Qt подключиться к системной шине (именно там запущен демон HAL), и вызывать необходимые методы D-Bus. В примере с Qt используются два полезных сигнала, предоставляемых HAL — DeviceAdded и DeviceRemoved. Первый срабатывает когда добавляется какое-либо устройство, второй — когда удаляется. Наглядными примерами могут послужить USB-флешки или DVD-диски. Когда такой сигнал возникает, вызываются все подписанные на него удалённые методы. Подписаться на эти сигналы можно средствами того же Qt.
<syntaxhighlight lang="c++">
- include <QDBusConnection>
- include <QDBusInterface>
- include <QDBusReply>
- include <QStringList>
- include <QDebug>
- include "hal.h"
namespace {
// функция вызова D-Bus методов, которые принимают один параметр и возвращают значение template<typename T> T dbusRequest(QDBusInterface &i, const QString &method, const QString ¶m) {
QDBusReply<T> reply = i.call(method, param); return reply.value();
}
}
HAL::HAL() : QTextEdit() {
resize(640, 480);
/* * Используем сигналы DeviceAdded и DeviceRemoved за слежением * за добавлением/удалением устройств. Следует помнить, что при добавлении, * скажем, USB флешки, в системе возникает несколько устройств. * Отделить нужные устройства от вспомогательных можно с помощью анализа * их свойств. */ QDBusConnection sys = QDBusConnection::systemBus();
sys.connect("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager", "org.freedesktop.Hal.Manager", "DeviceAdded", this, SLOT(slotAdded(const QString &)));
sys.connect("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager", "org.freedesktop.Hal.Manager", "DeviceRemoved", this, SLOT(slotRemoved(const QString &)));
// прочитаем список всех UDI устройств, и занесём этот список // в текстовое поле readDevices();
}
void HAL::readDevices() {
// с помощью QDBusInterface устанавливаем соединение с D-Bus шиной QDBusInterface i("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager", "org.freedesktop.Hal.Manager", QDBusConnection::systemBus());
// вызываем стандартный HAL метод - GetAllDevices. Этот // метод возвращает список всех обслуживаемых UDI в виде массива строк QDBusReply<QStringList> reply = i.call("GetAllDevices");
// ответ битый? if(reply.isValid()) { // получаем список строк из ответа QStringList l = reply.value(); QStringList::iterator itEnd = l.end();
// проходим по списку строк (UDI) и заносим каждую строку // в текстовое поле for(QStringList::iterator it = l.begin();it != itEnd;++it) append(*it); } else append("Failed to get device list");
}
void HAL::slotAdded(const QString &udi) {
/* * при добавлении устройства попробуем определить, что оно из себя * представляет (попробуем определить устройства с разделами, например USB * флеш накопители или DVD диски) */ QDBusInterface i("org.freedesktop.Hal", udi, "org.freedesktop.Hal.Device", QDBusConnection::systemBus());
QStringList caps = dbusRequest<QStringList>(i, "GetProperty", "info.capabilities");
qDebug() << "Added device with UDI" << udi << caps;
// устройство не имеет файловой системы, оно нас не интересует if(dbusRequest<QString>(i, "GetProperty", "volume.fsusage") != "filesystem" && !dbusRequest<bool>(i, "GetProperty", "volume.disc.has_audio")) return;
QDBusMessage reply = i.call("GetProperty", "volume.size");
// получаем другие данные по устройству QString product = dbusRequest<QString>(i, "GetProperty", "info.product"); QString size = reply.arguments().first().toString(); QString parent = dbusRequest<QString>(i, "GetProperty", "block.storage_device");
// родитель содержит параметры устройства, как модель и шину, // к которой оно подключено QDBusInterface iParent("org.freedesktop.Hal", parent, "org.freedesktop.Hal.Device", QDBusConnection::systemBus());
QString model = dbusRequest<QString>(iParent, "GetProperty", "storage.model"); QString vendor = dbusRequest<QString>(iParent, "GetProperty", "storage.vendor"); QString bus = dbusRequest<QString>(iParent, "GetProperty", "storage.bus");
append(QString("Added device, model (%1), vendor (%2), bus (%3), product (%4), size (%5)") .arg(model).arg(vendor).arg(bus).arg(product).arg(size));
}
void HAL::slotRemoved(const QString &udi) {
append(QString("Removed device with UDI %1").arg(udi));
} </syntaxhighlight>
При вставке DVD-диска в текстовом поле должно появится сообщение типа:
Added device, model (_NEC DVD_RW ND-3540A), vendor (), bus (ide), product (Мой диск), size (4644896768)
Tcl
Не менее просто чем из Qt, с HAL можно работать и из Tcl. Пример можно посмотреть в разделе D-Bus.