systemd In Action, volume 4

Ivan Shapovalov, Like-all, Pavlo Rudyi

Part 1 Part 2 Part 3
Приветствуем всех.

В #3 нашего затяжного обзора возможностей systemd мы разбирались с systemd-journald — подсистемой сбора системных логов (называть его заменой syslogd не совсем корректно, хотя вести свой лог в виде бинарной БД он также умеет). Однако, часть аспектов работы с journald осталась неохваченной.

В связи с этим очередную часть systemd In Action мы начнём с двух вещей, с ним связанных. А именно:

  • оценить устойчивость нативного формата БД к произвольным повреждениям;
  • рассмотреть возможности journald (точнее, трёх вспомогательных утилит) по передаче логов по сети.

Итак,

The Journal, продолжение.

Устойчивость к повреждениям

Проверим, насколько нативный формат файлов journald устойчив к повреждениям. Для этого запишем 1 КиБ мусора в случайное место в файле системного лога и посмотрим, что изменится в выводе journalctl.

Для начала удостоверимся, что исходно файл лога не нарушен. Для этого воспользуемся штатным средством проверки целостности — journalctl --verify. По умолчанию этот режим (так же, как и вывод данных) производится над всеми лог-файлами в стандартных расположениях (/{var,run}/log/journal).

# journalctl --verify
PASS: /run/log/journal/fe39ba83b9244251b1704fc655fbff2f/system.journal
0015d0: invalid data hash table item (290/233016) head_hash_offset: b5d9a2492d2da8f7
0015d0: invalid object contents: Bad message
File corruption detected at /var/log/journal/fe39ba83b9244251b1704fc655fbff2f/system.journal:0015d0 (of 8388608 bytes, 0%).
FAIL: /var/log/journal/fe39ba83b9244251b1704fc655fbff2f/system.journal (Bad message)

В нашем случае логфайл уже был повреждён (в ходе предыдущих экспериментов). Простейший способ получить неповреждённый логфайл — удаление существующего и перезапуск машины, при котором тот пересоздастся и будет заполнен новыми записями. Собственно, делаем это.

# cd /var/log/journal/$(< /etc/machine-id )
# l
total 8.1M
drwxr-sr-x+ 2 root systemd-journal 4.0K Feb 21 17:50 .
drwxr-sr-x+ 4 root systemd-journal 4.0K Feb 19 19:40 ..
-rw-r-x---+ 1 root systemd-journal 8.0M Feb 21 17:50 system.journal

Имеем один файл минимального размера (изменение размера логфайлов производится дискретно, шагами по 8 МиБ). Проверяем целостность ещё раз:

# journalctl --verify
PASS: /var/log/journal/fe39ba83b9244251b1704fc655fbff2f/system.journal

Проверка успешна, как и ожидалось. Теперь экспортируем бинарный файл в подробный текстовый формат (KEY=VALUE по всем полям всех записей по очереди):

# journalctl -o export > uncorrupted

Дальше намеренно повреждаем файл system.journal, записав в него 1 КиБ случайных данных:

# dd if=/dev/urandom of=/var/log/journal/fe39ba83b9244251b1704fc655fbff2f/system.journal bs=1K count=1 seek=10 conv=notrunc
1+0 records in
1+0 records out
1024 bytes (1.0 kB) copied, 0.0022644 s, 452 kB/s

Собственно, так делать было неправильно. За то время, пока мы его повреждали, в файл успела добавиться ещё одна запись (о подгрузке в ядро модуля ГПСЧ).

Экспортируем данные еще раз:

# journalctl -o export > corrupted
# l corrupted
-rw-r--r--. 1 root root 871K Feb 21 17:53 corrupted

Как видим, экспорт повреждённого файла прошел успешно и на выходе мы получили файл ненулевой длины. Теперь сравниваем полученные текстовые файлы:

# diff -u uncorrupted corrupted
--- uncorrupted 2015-02-21 17:52:12.701000000 +0000
+++ corrupted   2015-02-21 17:53:30.576000000 +0000
@@ -25526,3 +25526,16 @@
_SYSTEMD_UNIT=session-3.scope
_SOURCE_REALTIME_TIMESTAMP=1424541030255833
+__CURSOR=s=00000000000000000000000000000000;i=560f;b=ca178527e7014a6c868084dac3960dd2;m=8d4be3a;t=50f9cd5ee7c96;x=fef0f211c7d6b5b0
+__REALTIME_TIMESTAMP=1424541162044566
+__MONOTONIC_TIMESTAMP=148160058
+_BOOT_ID=ca178527e7014a6c868084dac3960dd2
+_MACHINE_ID=fe39ba83b9244251b1704fc655fbff2f
+_HOSTNAME=systemd.cf
+_TRANSPORT=kernel
+PRIORITY=5
+SYSLOG_FACILITY=0
+SYSLOG_IDENTIFIER=kernel
+_SOURCE_MONOTONIC_TIMESTAMP=148159245
+MESSAGE=random: nonblocking pool is initialized

И вот этот результат мы интерпретировали неправильно, посчитав, что одна запись из повреждённого файла исчезла. На самом деле нет — одна запись была добавлена1.

На самом деле…

…всё не так радужно. При fuzz-тестировании произвольно взятого лог-файла достаточно простым скриптом, с некоторой степенью точности имитирующим посекторное повреждение, выясняется, что в некоторых случаях повреждение приводит к нечитаемости штатными средствами всех данных после места повреждения.

Тем не менее, необходимо подчеркнуть, что речь здесь идёт только о штатных средствах просмотра: утилите journalctl, которая принудительно прекращает чтение после первой полученной ошибки. Вполне вероятно, что ситуацию можно улучшить, исправив утилиту просмотра (или реализовав её аналог, ориентированный именно на восстановление).

Наконец, отметим, что больше информации о том, как работать с журналом, можно найти в journalctl(1), а список основных полей – в systemd.journal-fields(7). Ну и сам формат .journal-файлов тоже вполне документирован.

Возможности сетевого транспорта логов

Наверняка многие считают, что journald, в отличие от многих реализаций syslogd, не умеет передавать логи по сети и ограничен исключительно локальной их обработкой. Однако, это не совсем так. Сам по себе systemd-journald, действительно, ничего не знает про сеть и пишет либо в syslog-совместимый сокет, либо в файлы нативного формата. Тем не менее, в комплекте поставки systemd существуют три вспомогательные утилиты, с помощью которых и реализуется передача логов по сети в реальном времени “своими силами”.

Важное замечание: эти утилиты работают исключительно с логфайлами на диске (т. е. в нулевом приближении они похожи на tail -f | netcat). Поэтому для того, чтобы передавать логи по сети средствами journald, необходимо включить запись файлов в нативном формате — хотя бы в режиме Storage=volatile.

Итак, в systemd/journald существуют два способа передачи логов через сеть:

  • достаточно необычная pull-модель, при которой лог-сервер инициирует соединение и запрашивает данные, в то время как на самом деле сервер (systemd-journal-gatewayd) работает на источнике;
  • и более традиционная push-модель, когда лог-сервер в действительности является сервером, а открывает соединение и отправляет данные машина-источник (а именно утилита systemd-journal-upload).

На лог-сервере же в обоих случаях запускается systemd-journal-remote.

Данные передаются по сети поверх протокола HTTP(S) в подробном почти текстовом формате вида KEY=VALUE. Именно в этом формате логи отображаются командой journalctl -o export.

Способ первый — “pull”

В этом режиме на источнике логов мы запускаем самый настоящий специализированный HTTP-сервер systemd-journal-gatewayd (реализованный с помощью libmicrohttpd).

Вообще, у этого сервера нет даже конфигурационного файла: всё достигается прямым редактированием юнитов. Так, например, он представляет собой обычный сокет-активируемый сервис, поэтому чтобы изменить порт, на котором он будет принимать соединения, следует обратиться к юниту systemd-journal-gatewayd.socket и изменить в нём значение директивы ListenStream=. Или, например, для того, чтобы задать сертификат и секретный ключ HTTPS, достаточно изменить юнит systemd-journal-gatewayd.service, дописав в командную строку демона параметры --cert= и --key=.

Нас вполне устраивает стандартный порт 19531 (а настройку HTTPS для краткости мы опустим), поэтому сразу перейдём к запуску этого сервера на отдельной машине:

# systemctl start systemd-journal-gatewayd.socket

HTTP API этого сервера достаточно прост и описан в документации. В частности:

Адрес Ответ сервера
/browse интерактивная веб-консоль
/entries (основной метод) дамп журнала
/machine JSON-структура, описывающая систему (machine-id, boot-id, …)
/fields/<field> список всех значений, которые принимает поле <field> на данном участке журнала

Запрос /entries может принимать несколько URL-параметров, управляющих фильтрацией.

URL-параметр Значение
?boot эквивалент journalctl --this-boot
?follow эквивалент journalctl --follow
?discrete вернуть только ту запись, на которую указывает заголовок Range: (об этом чуть дальше)
?<field>=<value> добавить фильтр по значению поля <field>

При этом формат возвращаемых данных и требуемый диапазон записей задаются при помощи специальных HTTP-заголовков.

HTTP-заголовок Значение
Range: entries=<cursor>[[:<skip>]:<count>] выбор диапазона отображения: начать с курсора <cursor>, пропустить <skip> записей и вывести не более, чем <count> записей (по умолчанию — все или одну, в зависимости от параметра ?discrete)
Accept: <MIME-тип> выбор формата данных: например, text/plain (простой текст) или application.vnd.fdo.journal (journalctl -o export-подобный формат)

Сначала попробуем получить логи с тестовой машины, пользуясь только утилитой curl.

# curl -H"Accept: text/plain" "http://77.41.63.43:19531/entries?boot" > remote-current-boot-export

# curl -H"Accept: application/vnd.fdo.journal" "http://77.41.63.43:19531/entries?boot" > remote-current-boot-export
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 15.1M    0 15.1M    0     0   918k      0 --:--:--  0:00:16 --:--:--  930k

Как видим, это вполне себе нормальный протокол HTTP, с которым можно работать привычными средствами.

Теперь попробуем сделать то же самое, но уже с помощью утилиты systemd-journal-remote. “Комплектный” юнит запускает её в push-конфигурации, поэтому сейчас мы будем делать это вручную из консоли.

# lib/systemd/systemd-journal-remote --url=http://77.41.63.43:19531
Received 0 descriptors
Spawning curl http://77.41.63.43:19531/entries...
/var/log/journal/remote/remote-77.41.63.43:19531.journal: Successfully rotated journal
/var/log/journal/remote/remote-77.41.63.43:19531.journal: Successfully rotated journal

Собственно, внутри это тот же самый curl. Да, у этой утилиты конфигурационный файл уже есть: /etc/systemd/journal-remote.conf. С его помощью можно задать сертификат и секретный ключ HTTPS, а также (директивой SplitMode=) выбрать способ разбиения получаемых данных на отдельные файлы. Мы вернёмся к этой настройке при разборе push-конфигурации.

Помимо этих настроек, systemd-journal-remote позволяет указать в командной строке (параметром --output=) директорию или файл назначения, куда будут сохраняться получаемые от удалённой машины логи. Если этот параметр не указан, в качестве назначения по умолчанию будет взята директория /var/log/journal/remote, а выходной файл будет назван согласно hostname-части указанного URL.

На остальных параметрах этой утилиты мы останавливаться не будем — они все документированы в man-странице systemd-journal-remote(8). Но отдельно отметим, что просматривать лог-файлы в нестандартной директории можно с помощью команды journalctl -D <директория>. Обратите внимание, точно таким же способом можно получить доступ к логам не запущенной в данной момент системы (например, при восстановлении).

Способ второй — “push”

Теперь перейдём к рассмотрению более привычной модели взаимодействия, в которой соединение с лог-сервером инициируется машиной-источником логов.

Сначала запустим лог-сервер, представленный всё той же утилитой systemd-journal-remote. На этот раз мы будем запускать её с помощью комплектных юнитов, как обычный сокет-активируемый демон. Как и в предыдущем случае, для изменения порта или адреса, на котором будет создан сокет, предлагается напрямую править юнит systemd-journal-remote.socket.

Вернёмся к конфигурационному файлу этой утилиты.

[Remote]
# SplitMode=host
# ServerKeyFile=/etc/ssl/private/journal-remote.pem
# ServerCertificateFile=/etc/ssl/certs/journal-remote.pem
# TrustedCertificateFile=/etc/ssl/ca/trusted.pem

Директива SplitMode= (и параметр командной строки --split-mode=) позволяет указать, как сохранять данные, получаемые с разных хостов. Допустимы всего два значения:

Значение Описание
none записывать все получаемые логи сплошным потоком
host распределять логи по файлам в зависимости от hostname источника2

Итак, нас устраивает значение по умолчанию (host) и стандартный порт (на этот раз 19532), поэтому просто активируем .socket-юнит:

# systemctl start systemd-journal-remote.socket

После этого настроим и запустим на второй машине утилиту systemd-journal-upload. Для неё уже есть готовый юнит systemd-journal-upload.service, не требующий изменений, а также конфигурационный файл /etc/systemd/system/journal-upload.conf.

Нам остаётся всего лишь указать в этом файле (директивой URL=) адрес сервера, на который мы собственно хотим передавать данные:

[Upload]
URL= http://systemd.cf:19532
# ServerKeyFile=/etc/ssl/private/journal-upload.pem
# ServerCertificateFile=/etc/ssl/certs/journal-upload.pem
# TrustedCertificateFile=/etc/ssl/ca/trusted.pem

После чего запускаем systemd-journal-upload и проверяем работу сервиса.

# systemctl start systemd-journal-upload

Логи машины-источника:

<...>
фев 21 21:35:36 server-9-20 systemd[1]: Starting Journal Remote Upload Service...
<...>

Логи машины-сервера:

<...>
Feb 21 18:30:10 systemd.cf systemd[1]: Listening on Journal Remote Sink Socket.
Feb 21 18:30:10 systemd.cf systemd[1]: Starting Journal Remote Sink Socket.
<...>

Как видим — всё работает.

3. Настройка сети c помощью systemd

Дальше мы перейдем к systemd-networkd — настройке сети в формате и духе systemd. Конфигурационные файлы networkd расположены в директории /etc/systemd/network. Networkd имеет три типа конфигурационных файлов: *.link, *.network и *.netdev. Файлы настройки с расширением .link описывают физические параметры интерфейсов — каждый файл описывает один интерфейс: MAC-адресс, имя интерфейса, MTU, и прочие параметры, которые не относятся к сетевым. Эти файлы считываются каждый раз одним из обработчиков udev при запуске или перенастройке системы. Файлы с расширением .network считываются непосредственно демоном networkd и содержат сетевые параметры интерфейсов: IP-адреса, маршруты, шлюзы, DNS-сервера и прочее. И, наконец, файлы *.netdev служат для описания виртуальных интерфейсов.

Для демонстрации работы networkd мы выполним перевод конфигурации нашей машины с legacy sysv-initscript /etc/init.d/network на networkd. Напомним, что мы используем Fedora 21, systemd 219 и статическую конфигурацию сети:

$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 04:01:40:23:1f:01 brd ff:ff:ff:ff:ff:ff
    inet 188.166.46.238/18 brd 188.166.63.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 2a03:b0c0:2:d0::69:7001/64 scope global
       valid_lft forever preferred_lft forever
    inet6 fe80::601:40ff:fe23:1f01/64 scope link
       valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 04:01:40:23:1f:02 brd ff:ff:ff:ff:ff:ff
    inet 10.133.248.54/16 brd 10.133.255.255 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::601:40ff:fe23:1f02/64 scope link
       valid_lft forever preferred_lft forever

Выведем список интерфейсов, которые находятся под управлением networkd с помощью утилиты networkctl:

$ networkctl
IDX LINK             TYPE               OPERATIONAL SETUP
  1 lo               loopback           n/a         n/a
  2 eth0             ether              n/a         n/a
  3 eth1             ether              n/a         n/a

Мы увидели только список интерфейсов и их типов, так как сервис networkd у нас пока не запущен. После запуска перед нами предстанет немного другая картина:

# systemctl start systemd-networkd
# networkctl
IDX LINK             TYPE               OPERATIONAL SETUP
  1 lo               loopback           carrier     unmanaged
  2 eth0             ether              routable    unmanaged
  3 eth1             ether              routable    unmanaged

3 links listed.

Unmanaged говорит нам, что данный интерфейс пока не находится под управлением networkd, который вполне свободно допускает подобную практику. Посмотрим сетевые параметры интерфейсов, чтобы перенести их в конфигурацию networkd:

# cat /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE='eth0'
TYPE=Ethernet
BOOTPROTO=none
ONBOOT='yes'
HWADDR=04:01:40:23:1f:01
IPADDR=188.166.46.238
NETMASK=255.255.192.0
GATEWAY=188.166.0.1
NM_CONTROLLED='yes'
IPV6INIT=yes
IPV6ADDR=2A03:B0C0:0002:00D0:0000:0000:0069:7001/64
IPV6_DEFAULTGW=2A03:B0C0:0002:00D0:0000:0000:0000:0001
IPV6_AUTOCONF=no
DNS1=2001:4860:4860::8844
DNS2=2001:4860:4860::8888
DNS3=8.8.8.8

Для дистрибутивов, отличных от Red Hat-based эти конфигурационные файлы будут выглядеть иначе, но скорее всего нужную информацию можно получить без проблем. В текущей конфигурации интерфейс eth0 используется для доступа в интернет, а eth1 – в локальную сеть. Также рекомендуем перед запуском networkd переместить эти конфигурационные файлы в другую директорию, так как некоторые правила udev cчитывают их.

Перейдем к созданию link-файлов. У нас уже есть дефолтный link-файл /lib/systemd/network/99-default.link, в котором настраивается политики назначения MAC-адреса и именования сетевых интерфейсов.

[Link]
NamePolicy=kernel database onboard slot path
MACAddressPolicy=persistent

Создадим link-файлы для каждого интерфейса, где укажем интерфейсы и MAC-адреса:

cat /etc/systemd/network/90-external.link
[Match]
MACAddress=04:01:40:23:1f:01
[Link]
Name=eth-external
cat /etc/systemd/network/90-internal.link
[Match]
MACAddress=04:01:40:23:1f:02
[Link]
Name=eth-inner

Чтобы сетевые интерфейсы работали под управлением networkd, нам также необходимы конфигурационные файлы *.network:

cat eth-external.network
[Match]
Name= eth-outer
[Network]
DHCP=no
Adress=188.166.46.238/18
Adress=2A03:B0C0:0002:00D0:0000:0000:0000:0069:7001/64
Gateway=188.166.0.1
Gateway= 2A03:B0C0:0002:00D0:0000:0000:0000:0000:0001
DNS=2001:4860:4860:8844
DNS=2001:4860:4860:8888
DNS=8.8.8.8
cat eth-internal.network
[Match]
Name=eth-inner
[Network]
Address=10.133.248.54/16

Конфигурация готова, дальше остается отключить дефолтный сервис network и включить автозагрузку сервиса networkd:

# systemctl disable network && systemclt enable networkd.

Для уверенности, что данная конфигурация будет стабильно работать, перезапустим машину и посмотрим работает ли сеть:

$ ping systemd.cf
PING systemd.cf (188.166.46.238) 56(84) bytes of data.
64 bytes from systemd.cf (188.166.46.238): icmp_seq=6 ttl=56 time=48.7 ms
64 bytes from systemd.cf (188.166.46.238): icmp_seq=7 ttl=56 time=48.7 ms
64 bytes from systemd.cf (188.166.46.238): icmp_seq=8 ttl=56 time=48.4 ms

Ура, мы успешно настроили сеть с помощью networkd!

$ networkctl
IDX LINK             TYPE               OPERATIONAL SETUP
  1 lo               loopback           n/a         n/a
  2 eth-outer        ether              routable    configured
  3 eth-inner        ether              routable    configured

Теперь пришла пора обратить внимание на systemd-resolved – кеширующий DNS-сервер, точнее прослойка между glibc и DNS-серверами. Его конфигурационный файл /run/systemd/resolve/resolve.conf можно использовать только как симлинк к /etc/resolv.conf, так что изначально он не влияет на сетевую подсистему. Запустим resolved и покажем как его можно использовать в качестве кеширующего резолвера:

# systemctl enable systemd-resolvd && systemctl start systemd-resolvd

Зделаем симлинк на /etc/resolv.conf:

# ln -sf /run/systemd/resolve/resolve.conf /etc/resolve.conf

В отличие от таких программ как dnsmasq, resolved не является DNS-proxy – он не представляет виртуальный DNS-сервер, к которому можно отправлять запросы. Вместо этого он предоставляет nss-модуль, который встраивается в glibc, и позволяет любой программе использующей glibc, использовать кеш запросов. Управление осуществляется с помощью файла nssswitch.conf в секции hosts:

hosts: files dns myhostname mymashines

Краткое пояснение: Files – считывание с /etc/hosts, dns – встроеный glibc-резолвер c resolv.conf, myhostname&mymashine – nss модули поставляемые вместе с systemd.

myhostname – резолвит строку localhost и имя хоста, а mymachines ресолвит имена контейнеров и их адреса виртуальных интерфейсов.

Для использования resolved мы должны заменить модуль dns на resolve. Давайте проверим:

$ getenv hosts goo.gl
2a00:1450:4001:80f::1009 goo.gl

Мы получили IP-адрес с ~1с задержкой. При повторном запуске она должна исчезнуть – это означает, что кеш работает и адрес получается без помощи DNS-запроса. Более подробную информацию вы можете получить используя man systemd-networkd, man systemd.link, man systemd.network, man systemd.netdev, man systemd-resolved.

4. Busctl и базовая работа с dbus.

Дальше мы переходим к обзору небольших дополнительных утилит в systemd. У многих людей возникает мысль: “Зачем нужно столько утилит: timedated, hostnamed, localed и прочих? Они же написаны явно от нечего делать.” На самом деле подобные демоны раньше присутствовали в KDE: там был свой особый демон с поддержкой dbus и которому передавались различные параметры. Сейчас есть множество приложений, которые используют dbus в своей работе, также не можем не упомянуть policykit – менеджер прав доступа, который работает с dbus-вызовами и может принимать или отклонять их. Зачем все это нужно? Дело в том, что традиционный подход в виде suid-обертки или вообще без нее, требующий указания пароля суперпользователя – далеко не идеал. Например, для того чтобы сменить hostname DE просто делало форк скорее всего kdesudo hostname, этот процесс спрашивал пароль суперпользователя и в успешном случае все работало. Не слишком удобно, не так ли? Эта проблема решается с помощью микродемонодов, которые вы наблюдали раньше в составе systemd. Точнее не совсем демоны, потому что они активируются с помощью dbus и не работают все время.

Для начала мы продемонстрируем как обращаться к dbus-объектам и как устроена иерархия методов на dbus-шине. Для упрощения жизни у нас есть удобная утилита busctl, которая выгодно отличается от страшных на вид существующих аналогов предоставляющих доступ к dbus. В наших примерах мы будем также использовать утилиту dbus-send, которая есть почти везде. Для начала давайте получим список методов и свойств, предоставляемых главным обьектом systemd — org.freedesktop.systemd1:

$ busctl introspect org.freedesktop.systemd1 /org/freedesktop/systemd1
NAME                                TYPE      SIGNATURE        RESULT/VALUE
org.freedesktop.DBus.Introspectable interface -                -
.Introspect                         method    -                s
org.freedesktop.DBus.Peer           interface -                -
.GetMachineId                       method    -                s
.Ping                               method    -                -
org.freedesktop.DBus.Properties     interface -                -
.Get                                method    ss               v
.GetAll                             method    s                a{sv}
.Set                                method    ssv              -
.PropertiesChanged                  signal    sa{sv}as         -
org.freedesktop.systemd1.Manager    interface -                -
.AddDependencyUnitFiles             method    asssbb           a(sss)
.CancelJob                          method    u                -
.ClearJobs                          method    -                -
.CreateSnapshot                     method    sb               o
.DisableUnitFiles                   method    asb              a(sss)
.Dump                               method    -                s
.EnableUnitFiles                    method    asbb             ba(sss)
.Exit                               method    -                -
.GetDefaultTarget                   method    -                s
.GetJob                             method    u                o
.GetUnit                            method    s                o

Аналогичная операция с помощью утилиты dbus-send будет выглядеть так:

# dbus-send --system --type=method_call --print-reply --dest=org.freedesktop.systemd1 /org/freedesktop/systemd1 /org/freedesktop.DBus.Introspectable.Introspect

Мы обращаемся к системной шине(–system), дальше производим вызов метода(--type=method_call) и желаем получить ответ; с помощью флага --dest мы указываем обьект, к которому отправляем этот вызов. Дальше мы указываем путь внутри этого обьекта(/org/freedesktop.DBus.Introspectable) и интерфейс с методом в последнем флаге. Продемонстрируем часть вывода:

...
<signal name="StartupFinished">
 <arg type="t"/>
 <arg type="t"/>
 <arg type="t"/>
 <arg type="t"/>
 <arg type="t"/>
 <arg type="t"/>
</signal>
<signal name="UnitFilesChanged">
</signal>
<signal name="Reloading">
 <arg type="b"/>
</signal>
</interface>
</node>

Какой вариант более лаконичен и удобен судить вам, но думаю что это совершенно очевидно. Мы видим четыре интерфейса, которые реализуются этим обьектом. Каждый интерфейс является совокупностью методов, свойств и сигналов, также у них есть сигнатуры и возвращаемый результат. Для понимания практического применения давайте сменим дату в нашей операционной системе с помощью dbus-интерфейса timedated1.

$ busctl introspect org.freedesktop.timedate1 /org/freedesktop/timedate1
NAME                                TYPE      SIGNATURE RESULT/VALUE     FLAGS
org.freedesktop.DBus.Introspectable interface -         -                -
.Introspect                         method    -         s                -
org.freedesktop.DBus.Peer           interface -         -                -
.GetMachineId                       method    -         s                -
.Ping                               method    -         -                -
org.freedesktop.DBus.Properties     interface -         -                -
.Get                                method    ss        v                -
.GetAll                             method    s         a{sv}            -
.Set                                method    ssv       -                -
.PropertiesChanged                  signal    sa{sv}as  -                -
org.freedesktop.timedate1           interface -         -                -
.SetLocalRTC                        method    bbb       -                -
.SetNTP                             method    bb        -                -
.SetTime                            method    xbb       -                -
.SetTimezone                        method    sb        -                -
.CanNTP                             property  b         true             -
.LocalRTC                           property  b         false            emits-change
.NTP                                property  b         true             emits-change
.NTPSynchronized                    property  b         true             -
.RTCTimeUSec                        property  t         1432108225000000 -
.TimeUSec                           property  t         1432108225986680 -
.Timezone                           property  s         "Europe/Kiev"    emits-change

Обратите внимание на свойства обьекта: текущее время, временная зона, параметры NTP-синхронизации и прочее. Нам нужен метод .SetTime, который принимает три параметра, описанные в сигнатуре: время в микросекундах, на которое мы должны сдвинуться; нужность авторизации; сдвиг во времени или установка времени начиная от начала эпохи.

Давайте сдвинем время на 1 час вперед. Для этого воспользуемся утилитой busctl, а для возвращения к текущему времени — dbus-send.

# busctl call org.freedesktop.timedate1 /org/freedesktop/timedate1 org.freedesktop.timedate1 SetTime xbb $((3600*1000*1000)) true false

Алгоритм тот же: обьект на шине, путь внутри этого обьекта, интерфейс(совпадает с именем обьекта), имя метода и параметры. Первый параметр – сдвиг времени в микросекундах, дальше мы устанавливаем время относительно текущего и не используем относительную авторизацию относительно policykit, так как выполняем запрос от суперпользователя.

Посмотрим изменилось ли время c помощью dbus для единства стиля:

$ busctl call org.freedesktop.timedate1 /org/freedesktop/timedate1
...
.RTCTimeUSec                        property  t         1424552366000000 -
.TimeUSec                           property  t         1424552366243943 -
...

Как видим, все работает. Тепер вернем время вспять с помощью dbus-send:

# dbus-send --print-reply --system --type=method_call --dest=org.freedesktop.timedate1 /org/freedesktop/timedate1 org.freedesktop.timedate1.SetTime int64:$((-3600*1000*1000))  boolean:true boolean:false

Прочитать больше про D-Bus API systemd можно здесь. Будут полезными также man D-Bus, man kdbus, man sd-bus, man busctl.

Примечания


  1. При оценке устойчивости .journal-файлов к повреждениям мы неверно интерпретировали полученный дифф из одной записи. Вообще говоря, он был не в ту сторону (т. е. в повреждённом журнале оказалось на одну запись больше, причём в самом конце, что вполне объяснимо).Повреждение, судя по всему, попало в область вспомогательных хэш-таблиц, которые при простом итерировании не используются.
  2. Было неверно сказано, что .journal-файлы распределяются по поддиректориям согласно machine-id источников. На самом деле такая иерархия используется только для хранения логов локальных контейнеров.

 
Part 1 Part 2 Part 3

  • M Sergey

    Большое человеческое спасибо за цикл видео systemd in action!
    Вы поменяли мое отношение к данному проэкту ) Раньше я его выпиливал сразу после установки сервера )

    Ждем продолжения по данной теме, например об направлении firewall, управления контейнерами.

    • Paul Alberto Rufous

      К сожалению основные участники потеряли интерес, так что ждать возможно придется долго.

      • M Sergey

        Жаль.