C++の応用 - Systemd

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動

概要

サービスとは、PCの起動時に自動的に実行され、バックグラウンドで仕事をするために待機するソフトウェアのことである。

一般的に、サービスはグラフィカルユーザインターフェースを持たず、ユーザの操作無しに動作する。
最もよく知られているサービスは、Web、メール、データベース等のサーバで、Apache、MySQL等がある。

また、ハードウェアの検出やUSBメモリの自動統合(マウント)等もサービスによって行われる。

サービスには、システム起動時に関連するタスクやハードウェアに関連するタスクを行う"内部サービス"と、
その後にユーザがインストールするサービス(通常は全てのサーバサービスを含む)の2種類がある。

技術用語やコンピュータ用語では、サービスは伝統的にデーモンと呼ばれている。
そのため、サーバコンポーネントであるsshdやmysqldのように、サービスを表すプログラムの最後の文字として"d"が用いられることが多い。

一方、Systemdは、システムおよびセッションマネージャ(initシステム)であり、
コンピュータの起動プロセスからシャットダウンまでの全動作時間にわたって、システム上で動作するすべてのサービスを管理する役割を担っている。

プロセスは常に(可能な限り)並行して起動され、起動プロセスを可能な限り短くする。
ここで、.serviceで終わる設定ファイルを作成して、Systemdが制御・監視するプロセスに関するコードを保持する場合をSystemd Service Unitファイルと呼ぶ。

Systemdには、サービス、タイマ、マウントポイント、ソケット、スワップスペース、デバイス等のユニットが存在する。
そのため、Systemdは管理用の設定の全てをファイルから取得する。

Systemdの用語では、これらを"ユニット"と呼び、システム全体に適用されるユニットと各ユーザ領域にのみ適用されるユニットがある。

ユニットには、サービスを開始するためのサービスユニットや、ある時点でのアクションを(繰り返し)実行するためのタイマユニット等、様々な種類がある。

各タイプのユニットファイルに共通しているのは、iniファイルに似た構造をしていることである。
ユニットファイルは、いくつかのセクション(多くの場合、3セクション)で構成されている。

Systemdではセクションと呼ばれ、その中に一連のキーと値のペア(Systemdではディレクティブと呼ばれる)が格納されている。


Systemdライブラリのインストール

ライセンス

Systemdライブラリのライセンスは、LGPL 2.1以降、または、 GPL 2.0で利用可能である。

パッケージ管理システムからインストール

# RHEL
sudo dnf install systemd-devel

# SUSE
sudo zypper install systemd-devel


ソースコードからインストール

Systemdのビルドに必要なライブラリをインストールする。

sudo zypper install meson ninja python3-Jinja2 glib2-devel dbus-1-devel p11-kit-devel libarchive-devel pcre2-devel libcurl-devel libcap-devel \
                    libmount-devel libfdisk-devel libblkid-devel libdw-devel libpwquality-devel passwdqc-devel libkmod-devel libbpf-devel \
                    zlib-devel liblz4-devel libzstd-devel xz-devel libbz2-devel \
                    pam-devel libgnutls-devel libopenssl-devel libopenssl-1_1-devel libcryptsetup-devel libgcrypt-devel libgpg-error-devel \
                    qrencode-devel libiptc-devel libidn2-devel libmicrohttpd-devel \
                    libxkbcommon-devel libfido2-devel tpm2-0-tss-devel libseccomp-devel libacl-devel audit-devel \
                    libapparmor-devel  # AppArmorを使用する場合
                    libselinux-devel   # SELinuxを使用する場合
                    xen-devel          # Xenを使用する場合


SystemdのGithubにアクセスして、ソースコードをダウンロードする。
ダウンロードしたファイルを解凍する。

tar xf systemd-<バージョン>.tar.gz
cd systemd-<バージョン>


Systemdをビルドおよびインストールする。

meson --prefix=<Systemdのインストールディレクトリ> -Dmode=release build
ninja -C ./build -j $(nproc)
ninja -C ./build install



Systemdの使用例 : SSHサービスの起動

Systemdを利用してサービスを起動する場合は、Systemdのライブラリを使用する必要がある。

以下の例では、Systemdのライブラリを使用してSSHサービスを起動している。

  1. sd_bus_open_system関数を使用して、システムバスへの接続を開く。
    戻り値が負の値の場合は、接続に失敗したことを示す。
  2. sd_bus_call_method関数を使用して、SystemdのD-Busインターフェイスを介してsshd.serviceを起動する。
    関数の引数には、バスの接続、D-Busサービス名、D-Busオブジェクトのパス名、インターフェイス名、メソッド名、入力引数、出力引数、起動するサービス名とモードを指定する。
    戻り値が負の値の場合は、サービスの起動に失敗したことを示す。
    サービスの起動に成功した場合は、成功メッセージをデバッグ出力に表示している。
  3. sd_bus_unref関数を使用して、バスの接続を閉じる。


また、RAIIパターンを使用することにより、例外が発生した場合でもリソースが確実に解放されることを保証している。

このサンプルコードを実行するには、Systemdライブラリがインストールされている必要がある。
また、適切な権限でプログラムを実行する必要がある。

※注意
この方法は、Systemdが利用可能なシステムでのみ動作する。

  • CMakeを使用する場合
 # Package Configの使用
 find_package(PkgConfig REQUIRED)
 
 # Systemdライブラリの使用
 pkg_check_modules(SYSTEMD REQUIRED libsystemd)
 
 ## Systemdライブラリのバージョンを指定する場合
 #pkg_check_modules(SYSTEMD REQUIRED libsystemd >= <バージョン>)
 
 target_include_directories(<your_target> PUBLIC
    # ...略
    ${SYSTEMD_INCLUDE_DIRS}
 )
 
 target_link_libraries(<your_target>
    # ...略
    ${SYSTEMD_LIBRARIES}
 )


 #include <iostream>
 #include <string>
 #include <cstring>
 #include <system_error>
 #include <stdexcept>
 #include <systemd/sd-bus.h>
 
 // システムエラーを処理するためのカスタム例外クラス
 class SystemdError : public std::runtime_error
 {
 private:
    int error_code_;
 
 public:
    explicit SystemdError(const std::string &message, int error_code) : std::runtime_error(message + ": " + std::string(strerror(-error_code)))
       , error_code_(-error_code)
    {}
 
    int error_code() const { return error_code_; }
 };
 
 class ScopedBusConnection
 {
 private:
    sd_bus* bus_;
 
 public:
    // D-Bus接続を確立して、RAIIでリソース管理を行う
    explicit ScopedBusConnection() : bus_(nullptr)
    {
       int ret = sd_bus_open_system(&bus_);
       if (ret < 0) {
          throw SystemdError("D-Bus接続の確立に失敗", ret);
       }
    }
 
    // デストラクタでD-Bus接続を自動的に解放
    ~ScopedBusConnection()
    {
       if (bus_) {
          sd_bus_unref(bus_);
       }
    }
 
    // D-Busハンドルを取得
    sd_bus* get() { return bus_; }
 
    // コピーを禁止
    ScopedBusConnection(const ScopedBusConnection&)            = delete;
    ScopedBusConnection& operator=(const ScopedBusConnection&) = delete;
 };
 
 // SSHサービスを起動する関数
 void start_ssh_service(sd_bus* bus)
 {
    if (!bus) {
       throw std::invalid_argument("無効なD-Busハンドルが渡されました");
    }
 
    int ret = sd_bus_call_method(bus,
                                 "org.freedesktop.systemd1",           // サービス名
                                 "/org/freedesktop/systemd1",          // オブジェクトパス
                                 "org.freedesktop.systemd1.Manager",   // インターフェース名
                                 "StartUnit",                          // メソッド名
                                 nullptr,                              // エラー戻り値
                                 nullptr,                              // 戻り値
                                 "ss",                                 // 引数の型(string, string)
                                 "sshd.service",                       // 起動するサービス名
                                 "replace");                           // 起動モード
 
    if (ret < 0) {
       throw SystemdError("SSHサービスの起動に失敗しました", ret);
    }
 }
 
 int main(int argc, char *argv[])
 {
    try {
       // RAIIを使用してD-Bus接続を管理
       ScopedBusConnection bus_connection;
 
       // SSHサービスを起動
       start_ssh_service(bus_connection.get());
 
       std::cout << "SSHサービスの起動に成功しました" << std::endl;
 
       return 0;
    }
    catch (const SystemdError &e) {
       // システム関連のエラーを処理
       std::cerr << "システムエラーが発生: " << e.what() << std::endl;
       std::cerr << "エラーコード: " << e.error_code() << std::endl;
       return -1;
    }
    catch (const std::exception &e) {
       // その他の一般的なエラーを処理
       std::cerr << "予期せぬエラーが発生: " << e.what() << std::endl;
       return -2;
    }
    catch (...) {
       // 未知のエラーを処理
       std::cerr << "不明なエラーが発生" << std::endl;
       return -3;
    }
 }