色々試行錯誤したのでメモ。
dbus crate を使う。
開発中の libspecinfra で、systemd 配下の service の状態を取得できるようにするための provider を書こうと思い、色々調べたりコード書いて試したりした。
まずは目的に合う crates がないか crates.io で検索。ざっと以下のようなものが見つかる。
systemd crate は libsystemd の Rust インターフェースで、sd-daemon、sd-id128、sd-journal、sd-login に対応している。が sd-bus は まだ実装が不完全 なようなので、目的には合わなさそう、と判断。
systemd-dbus crate は2年半以上更新がなく、rust 1.19.0 でコンパイルが通らなかったので断念。
他に systemd を直接扱える、目的に適いそうな crate が見当たらなかったので、D-Bus が扱える crate ってことで、dbus crate と dbus-bytestream create を試してみることにした。
dbus crate にしても dbus-bytestream crate にしても、ざっとドキュメントやコードを読んだ感じ、D-Bus でどういった形でメッセージのやりとりがなされているのかを理解しないと、使うのは無理だなこれは、と思ったので、まずは dbus-send コマンドで必要な情報が得られるかどうかトライしてみた。
libspecinfra でやりたいことのひとつは、サービスが動いているかどうかを調べること。dbus-send では以下のような形で実行すれば、この情報が得られることがわかった。
まずはユニット名(ssh.service)からオブジェクトパス(/org/freedesktop/systemd1/unit/ssh_2eservice)を取得。
$ dbus-send --system \
--dest=org.freedesktop.systemd1 \
--type=method_call \
--print-reply \
/org/freedesktop/systemd1 \
org.freedesktop.systemd1.Manager.GetUnit \
string:ssh.service
method return time=1507693363.257785 sender=:1.10 -> destination=:1.220 serial=3078 reply_serial=2
object path "/org/freedesktop/systemd1/unit/ssh_2eservice"
--dest=org.freedesktop.systemd1
で接続先のバス名を指定。/org/freedesktop/systemd1
が操作対象のオブジェクトパス、org.freedesktop.systemd1.Manager
がインターフェースで、それに続く GetUnit
が呼び出すメソッド、string:ssh.service
がメソッドに渡す引数、という形式になっている。
このオブジェクトパスの ActiveState プロパティを取得。
$ dbus-send --system \
--dest=org.freedesktop.systemd1 \
--type=method_call \
--print-reply \
/org/freedesktop/systemd1/unit/ssh_2eservice \
org.freedesktop.DBus.Properties.Get \
string:org.freedesktop.systemd1.Unit \
string:ActiveState
method return time=1507693392.955268 sender=:1.10 -> destination=:1.221 serial=3079 reply_serial=2
variant string "active"
これでサービスの状態を得ることができた。
どのようなメソッドやプロパティがあるかは The D-Bus API of systemd/PID 1 に載っている。
dbus crate を使って以下のようなコードを書けば良い。
extern crate dbus; | |
use dbus::{Connection, BusType, Message, Path}; | |
use dbus::arg; | |
fn main() { | |
// Get object path by service name | |
let c = Connection::get_private(BusType::System).unwrap(); | |
let m = Message::new_method_call("org.freedesktop.systemd1", | |
"/org/freedesktop/systemd1", | |
"org.freedesktop.systemd1.Manager", | |
"GetUnit") | |
.unwrap() | |
.append1("ssh.service"); | |
let r = c.send_with_reply_and_block(m, 2000).unwrap(); | |
let o: Path = r.read1().unwrap(); | |
println!("{}", o); | |
// Get active state of the service | |
let m = Message::new_method_call("org.freedesktop.systemd1", | |
o, | |
"org.freedesktop.DBus.Properties", | |
"Get") | |
.unwrap() | |
.append2("org.freedesktop.systemd1.Unit", "ActiveState"); | |
let r = c.send_with_reply_and_block(m, 2000).unwrap(); | |
let s: arg::Variant<&str> = r.read1().unwrap(); | |
println!("{}", s.0); | |
} |
試したコードは Cargo.toml 等も含めて GitHub に置いてある。
dbus crate は サンプルコード もあるし、ドキュメント もあるので、ドキュメントがほとんどない dbus-bytestream crate と比べると、比較的扱いやすいように思える(といっても、ドキュメント読めばすべてわかる、というわけではなく、コードも読んで色々試行錯誤したけど)。
dbus-bytestream も同時に試したけど、取得した ActiveState の情報をデコードする方法がよくわからないまま、dbus crate の方が動いたので、途中で断念した。なので、GitHub 上にある dbus-bytestream を使ったコードはエラーで動かない。