如何使用 sd-bus 获取服务状态?
How to get the state of a service with sd-bus?
我需要从 C++ 应用程序查询、监视并可能更改一些系统服务的状态。看起来 sd-bus 是正确的方法,但我很难找到一个例子。
那么,我该如何:
1) 通过sd-bus查询服务当前状态,类似systemctl status foo.service
?
2) 监控服务的状态,以便在它发生变化时得到回调?
3) 改变一个服务的状态,类似于systemctl start/stop/restart
?
谢谢!
使用sd-bus
API绝对正确(header#include <systemd/sd-bus.h>
)
首先您需要乘坐公交车object:
我这样做:
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/group 访问此命令的权限
- 运行
_user
总线而不是 _system
总线
完成后别忘了释放总线:
Systemctl::~Systemctl()
{
sd_bus_unref(m_bus);
}
现在你有 3 个问题:
- 查询状态
对于每个单元,我都有一个 class,其中包含作为 m_name
的转义名称 (foo_2eservice
),以及对 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;
}
- 监控服务状态:
第一步是设置一个 file-descriptor 来订阅更改。在这种情况下,您有兴趣订阅 "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);
}
我需要从 C++ 应用程序查询、监视并可能更改一些系统服务的状态。看起来 sd-bus 是正确的方法,但我很难找到一个例子。
那么,我该如何:
1) 通过sd-bus查询服务当前状态,类似systemctl status foo.service
?
2) 监控服务的状态,以便在它发生变化时得到回调?
3) 改变一个服务的状态,类似于systemctl start/stop/restart
?
谢谢!
使用sd-bus
API绝对正确(header#include <systemd/sd-bus.h>
)
首先您需要乘坐公交车object:
我这样做:
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/group 访问此命令的权限
- 运行
_user
总线而不是_system
总线
完成后别忘了释放总线:
Systemctl::~Systemctl()
{
sd_bus_unref(m_bus);
}
现在你有 3 个问题:
- 查询状态
对于每个单元,我都有一个 class,其中包含作为 m_name
的转义名称 (foo_2eservice
),以及对 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;
}
- 监控服务状态:
第一步是设置一个 file-descriptor 来订阅更改。在这种情况下,您有兴趣订阅 "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);
}