blob: e628d249e332b5c89f4a821adc78b27eabbcecc6 [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2020 YADRO
#include "dbus_loop.hpp"
#include <phosphor-logging/log.hpp>
#include <system_error>
using namespace phosphor::logging;
DbusLoop::DbusLoop() : bus(nullptr), event(nullptr)
{
int rc;
rc = sd_bus_default(&bus);
if (rc < 0)
{
std::error_code ec(-rc, std::generic_category());
throw std::system_error(ec, "Unable to initiate D-Bus connection");
}
rc = sd_event_default(&event);
if (rc < 0)
{
sd_bus_unref(bus);
std::error_code ec(-rc, std::generic_category());
throw std::system_error(ec, "Unable to create D-Bus event loop");
}
rc = sd_bus_attach_event(bus, event, SD_EVENT_PRIORITY_NORMAL);
if (rc < 0)
{
sd_bus_unref(bus);
sd_event_unref(event);
std::error_code ec(-rc, std::generic_category());
throw std::system_error(ec, "Unable to attach D-Bus event");
}
}
DbusLoop::~DbusLoop()
{
sd_bus_unref(bus);
sd_event_unref(event);
}
int DbusLoop::run() const
{
return sd_event_loop(event);
}
void DbusLoop::stop(int code) const
{
sd_event_exit(event, code);
}
void DbusLoop::addPropertyHandler(const std::string& objPath,
const WatchProperties& props,
std::function<void()> callback)
{
// Add match handler
const int rc = sd_bus_match_signal(bus, nullptr, nullptr, objPath.c_str(),
"org.freedesktop.DBus.Properties",
"PropertiesChanged", msgCallback, this);
if (rc < 0)
{
std::error_code ec(-rc, std::generic_category());
throw std::system_error(ec, "Unable to register property watcher");
}
propWatch = props;
propHandler = callback;
}
void DbusLoop::addIoHandler(int fd, std::function<void()> callback)
{
ioHandler = callback;
const int rc = sd_event_add_io(event, nullptr, fd, EPOLLIN,
&DbusLoop::ioCallback, this);
if (rc < 0)
{
std::error_code ec(-rc, std::generic_category());
throw std::system_error(ec, "Unable to register IO handler");
}
}
void DbusLoop::addSignalHandler(int signal, std::function<void()> callback)
{
// Block the signal
sigset_t ss;
if (sigemptyset(&ss) < 0 || sigaddset(&ss, signal) < 0 ||
sigprocmask(SIG_BLOCK, &ss, nullptr) < 0)
{
std::error_code ec(errno, std::generic_category());
std::string err = "Unable to block signal ";
err += strsignal(signal);
throw std::system_error(ec, err);
}
signalHandlers.insert(std::make_pair(signal, callback));
// Register handler
const int rc = sd_event_add_signal(event, nullptr, signal,
&DbusLoop::signalCallback, this);
if (rc < 0)
{
std::error_code ec(-rc, std::generic_category());
std::string err = "Unable to register handler for signal ";
err += strsignal(signal);
throw std::system_error(ec, err);
}
}
int DbusLoop::msgCallback(sd_bus_message* msg, void* userdata,
sd_bus_error* /*err*/)
{
const WatchProperties& propWatch =
static_cast<DbusLoop*>(userdata)->propWatch;
try
{
int rc;
// Filter out by interface name
const char* interface;
rc = sd_bus_message_read(msg, "s", &interface);
if (rc < 0)
{
std::error_code ec(-rc, std::generic_category());
throw std::system_error(ec, "Unable to read interface name");
}
const auto& itIface = propWatch.find(interface);
if (itIface == propWatch.end())
{
return 0; // Interface is now watched
}
const Properties& props = itIface->second;
// Read message: go through list of changed properties
rc = sd_bus_message_enter_container(msg, SD_BUS_TYPE_ARRAY, "{sv}");
if (rc < 0)
{
std::error_code ec(-rc, std::generic_category());
throw std::system_error(ec, "Unable to open message container");
}
while ((rc = sd_bus_message_enter_container(msg, SD_BUS_TYPE_DICT_ENTRY,
"sv")) > 0)
{
// Get property's name
const char* name;
rc = sd_bus_message_read(msg, "s", &name);
if (rc < 0)
{
sd_bus_message_exit_container(msg);
std::error_code ec(-rc, std::generic_category());
throw std::system_error(ec, "Unable to get property name");
}
// Get and check property's type
const char* type;
rc = sd_bus_message_peek_type(msg, nullptr, &type);
if (rc < 0 || strcmp(type, "s"))
{
sd_bus_message_exit_container(msg);
continue;
}
// Get property's value
const char* value;
rc = sd_bus_message_enter_container(msg, SD_BUS_TYPE_VARIANT, type);
if (rc < 0)
{
sd_bus_message_exit_container(msg);
std::error_code ec(-rc, std::generic_category());
throw std::system_error(ec, "Unable to open property value");
}
rc = sd_bus_message_read(msg, type, &value);
if (rc < 0)
{
sd_bus_message_exit_container(msg);
sd_bus_message_exit_container(msg);
std::error_code ec(-rc, std::generic_category());
throw std::system_error(ec, "Unable to get property value");
}
sd_bus_message_exit_container(msg);
// Check property name/value and handle the match
const auto& itProps = props.find(name);
if (itProps != props.end() &&
itProps->second.find(value) != itProps->second.end())
{
static_cast<DbusLoop*>(userdata)->propHandler();
}
sd_bus_message_exit_container(msg);
}
sd_bus_message_exit_container(msg);
}
catch (const std::exception& ex)
{
log<level::WARNING>(ex.what());
}
return 0;
}
int DbusLoop::signalCallback(sd_event_source* /*src*/,
const struct signalfd_siginfo* si, void* userdata)
{
DbusLoop* instance = static_cast<DbusLoop*>(userdata);
const auto it = instance->signalHandlers.find(si->ssi_signo);
if (it != instance->signalHandlers.end())
{
it->second();
}
else
{
std::string msg = "Unhandled signal ";
msg += strsignal(si->ssi_signo);
log<level::WARNING>(msg.c_str());
}
return 0;
}
int DbusLoop::ioCallback(sd_event_source* /*src*/, int /*fd*/,
uint32_t /*revents*/, void* userdata)
{
static_cast<DbusLoop*>(userdata)->ioHandler();
return 0;
}