如何使用 sd-bus 获取服务状态?

Posted

技术标签:

【中文标题】如何使用 sd-bus 获取服务状态?【英文标题】:How to get the state of a service with sd-bus? 【发布时间】:2020-09-08 10:25:59 【问题描述】:

我需要从 C++ 应用程序查询、监控并可能更改一些 systemd 服务的状态。看起来 sd-bus 是正确的方法,但我很难找到一个例子。

那么,我该怎么做:

1) 通过sd-bus查询服务的当前状态,类似systemctl status foo.service?

2) 监控服务的状态,以便每当它发生变化时我都会收到回调?

3) 改变一个服务的状态,类似于systemctl start/stop/restart?

谢谢!

【问题讨论】:

这是一个非常大的问题(或三个)...对于 systemd1 API 的 sd-bus 使用示例,您可以查看 systemctl 本身(在 systemd 源代码中)——我会警告这是一个巨大的工具,所以可能不太容易理解。就 D-Bus 库而言,sd-bus 是一个相当低级的 API,而 GDBus(甚至 Qt)可能更容易使用。我建议您选择一个库并阅读手册:然后您应该能够将这些知识与 systemd1 API 一起使用,并在需要时提出具体问题。对于调试和测试,我建议使用 d-feet。 对于特定的 systemd1 API,请参阅freedesktop.org/wiki/Software/systemd/dbus,尤其是“单元对象”部分:Start() 和 Stop() 方法以及 LoadState 和 Substate 属性应该是有意义的 sd-bus 绝对是做到这一点的方式。这里有一篇很棒的博客文章:0pointer.de/blog/the-new-sd-bus-api-of-systemd.html。明天我会发布一个例子。我在这里的问题unix.stackexchange.com/questions/527283/…对此有一点答案。 【参考方案1】:

使用sd-bus API 绝对正确(标头#include <systemd/sd-bus.h>

首先你需要访问一个总线对象:

我这样做:

Systemctl::Systemctl() :
    m_bus(nullptr)

    int r = sd_bus_default_system(&m_bus);

    if (r < 0)
        throw exception("Could not open systemd bus");

如果您在打开巴士时遇到问题:

    以 root/sudo 身份运行 制定一些 polkit 策略以授予您的用户/组访问此命令的权限 运行_user 总线而不是_system 总线

完成后别忘了释放总线:

Systemctl::~Systemctl()

    sd_bus_unref(m_bus);

现在你有 3 个问题:

    查询状态

对于每个单元,我都有一个将转义名称 (foo_2eservice) 保存为 m_name 的类,并在 m_bus 中引用总线。使用任何属性调用此方法。您似乎对"ActiveState""SubState" 最感兴趣。

std::string Unit::GetPropertyString(const std::string& property) const

    sd_bus_error err = SD_BUS_ERROR_NULL;
    char* msg = nullptr;
    int r;

    r = sd_bus_get_property_string(m_bus,
        "org.freedesktop.systemd1",
        ("/org/freedesktop/systemd1/unit/" + m_unit).c_str(),
        "org.freedesktop.systemd1.Unit",
        property.c_str(),
        &err,
        &msg);

    if (r < 0)
    
        std::string err_msg(err.message);
        sd_bus_error_free(&err);

        std::string err_str("Failed to get " + property + " for service "
                            + m_name + ". Error: " + err_msg);

        throw exception(err_str);
    

    sd_bus_error_free(&err);

    // Free memory (avoid leaking)
    std::string ret(msg);
    free (msg);

    return ret;

    监控服务状态:

第一步是设置一个文件描述符来订阅更改。在这种情况下,您有兴趣订阅“PropertiesChanged”信号。请注意,您将收到任何属性更改的信号,而不仅仅是状态。在sd_bus_add_match() 调用中,有回调的空间,虽然我没有尝试过。

void Systemctl::SubscribeToUnitChanges(const std::string& escaped_name)

    /* This function is an easier helper, but it as only introduced in systemd 237
     * Stretch is on 232 while buster is on 241 .  Need re replace this as long as
     * we still support stretch
    sd_bus_match_signal(
        m_bus,
        nullptr, // slot
        nullptr, // sender
        std::string("/org/freedesktop/systemd1/unit/" + escaped_name).c_str(), // path
        "org.freedesktop.DBus.Properties", // interface
        "PropertiesChanged", // member
        nullptr, // callback
        nullptr // userdata
    );
    */
    std::string match =  "type='signal'";
        match += ",path='/org/freedesktop/systemd1/unit/" + escaped_name + "'" ;
        match += ",interface='org.freedesktop.DBus.Properties'";
        match += ",member='PropertiesChanged'";

    sd_bus_add_match(
        m_bus,
        nullptr, // slot
        match.c_str(),
        nullptr, // callback
        nullptr // userdata
    );

我所做的是定期轮询总线以获取订阅的更改并更新每个单元:

bool Systemctl::ProcessBusChanges()

    bool changed = false;
    sd_bus_message* msg = nullptr;

    // for each new message
    std::list<std::string> escaped_names;
    while( sd_bus_process(m_bus, &msg) )
    
        // Note:  Once sd_bus_process returns 0, We are supposed to call
        // sd_bus_wait, or check for changes on sd_bus_get_fd before calling
        // this function again.  We're breaking that rule.  I don't really know
        // the consequences.
        if (msg)
        
            std::string path = strna( sd_bus_message_get_path(msg) );
            sd_bus_message_unref(msg);

            std::string escaped_name = path.erase(0, path.find_last_of('/')+1 );
            escaped_names.push_back(escaped_name);

            changed = true;
        
    

    escaped_names.sort();
    escaped_names.unique();
    for (auto unit : escaped_names)
    
        auto it = m_units.find(unit);
        if (it != m_units.end())
            it->second.RefreshDynamicProperties();
    

    return changed;

如果它告诉我们总线发生了变化,那么我继续读取该总线上我所有受监控的单元。

    更改状态

这个很简单。我使用以下,其中method"StartUnit""StopUnit""RestartUnit" 之一。

static void CallMethodSS(sd_bus* bus,
                         const std::string& name,
                         const std::string& method)

    sd_bus_error err = SD_BUS_ERROR_NULL;
    sd_bus_message* msg = nullptr;
    int r;

    r = sd_bus_call_method(bus,
        "org.freedesktop.systemd1",         /* <service>   */
        "/org/freedesktop/systemd1",        /* <path>      */
        "org.freedesktop.systemd1.Manager", /* <interface> */
        method.c_str(),                     /* <method>    */
        &err,                               /* object to return error in */
        &msg,                               /* return message on success */
        "ss",                               /* <input_signature (string-string)> */
        name.c_str(),  "replace" );         /* <arguments...> */

    if (r < 0)
    
        std::string err_str("Could not send " + method +
                            " command to systemd for service: " + name +
                            ". Error: " + err.message );

        sd_bus_error_free(&err);
        sd_bus_message_unref(msg);
        throw exception(err_str);
    

    // Extra stuff that might be useful:  display the response...
    char* response;
    r = sd_bus_message_read(msg, "o", &response);
    if (r < 0)
    
      LogError("Failed to parse response message: %s\n", strerror(-r) );
    

    sd_bus_error_free(&err);
    sd_bus_message_unref(msg);

【讨论】:

以上是关于如何使用 sd-bus 获取服务状态?的主要内容,如果未能解决你的问题,请参考以下文章

systemd 的 sd-bus 详细文档

如何定期使用多线程获取服务器状态

libuv 中 sd-bus 的事件循环处理

如何通过 CLI 获取 Wildfly 服务器状态?

sd-bus.h 例子

如何使用 RxSwift 处理应用程序状态