Artem Senichev | e8837d5 | 2020-06-07 11:59:04 +0300 | [diff] [blame] | 1 | // SPDX-License-Identifier: Apache-2.0 |
| 2 | // Copyright (C) 2020 YADRO |
| 3 | |
| 4 | #include "dbus_loop.hpp" |
| 5 | |
| 6 | #include <phosphor-logging/log.hpp> |
| 7 | |
| 8 | #include <system_error> |
| 9 | |
| 10 | using namespace phosphor::logging; |
| 11 | |
| 12 | DbusLoop::DbusLoop() : bus(nullptr), event(nullptr) |
| 13 | { |
| 14 | int rc; |
| 15 | |
| 16 | rc = sd_bus_default(&bus); |
| 17 | if (rc < 0) |
| 18 | { |
| 19 | std::error_code ec(-rc, std::generic_category()); |
| 20 | throw std::system_error(ec, "Unable to initiate D-Bus connection"); |
| 21 | } |
| 22 | |
| 23 | rc = sd_event_default(&event); |
| 24 | if (rc < 0) |
| 25 | { |
| 26 | sd_bus_unref(bus); |
| 27 | std::error_code ec(-rc, std::generic_category()); |
| 28 | throw std::system_error(ec, "Unable to create D-Bus event loop"); |
| 29 | } |
| 30 | |
| 31 | rc = sd_bus_attach_event(bus, event, SD_EVENT_PRIORITY_NORMAL); |
| 32 | if (rc < 0) |
| 33 | { |
| 34 | sd_bus_unref(bus); |
| 35 | sd_event_unref(event); |
| 36 | std::error_code ec(-rc, std::generic_category()); |
| 37 | throw std::system_error(ec, "Unable to attach D-Bus event"); |
| 38 | } |
| 39 | } |
| 40 | |
| 41 | DbusLoop::~DbusLoop() |
| 42 | { |
| 43 | sd_bus_unref(bus); |
| 44 | sd_event_unref(event); |
| 45 | } |
| 46 | |
| 47 | int DbusLoop::run() const |
| 48 | { |
| 49 | return sd_event_loop(event); |
| 50 | } |
| 51 | |
| 52 | void DbusLoop::stop(int code) const |
| 53 | { |
| 54 | sd_event_exit(event, code); |
| 55 | } |
| 56 | |
| 57 | void DbusLoop::addPropertyHandler(const std::string& objPath, |
| 58 | const WatchProperties& props, |
| 59 | std::function<void()> callback) |
| 60 | { |
| 61 | // Add match handler |
| 62 | const int rc = sd_bus_match_signal(bus, nullptr, nullptr, objPath.c_str(), |
| 63 | "org.freedesktop.DBus.Properties", |
| 64 | "PropertiesChanged", msgCallback, this); |
| 65 | if (rc < 0) |
| 66 | { |
| 67 | std::error_code ec(-rc, std::generic_category()); |
| 68 | throw std::system_error(ec, "Unable to register property watcher"); |
| 69 | } |
| 70 | |
| 71 | propWatch = props; |
| 72 | propHandler = callback; |
| 73 | } |
| 74 | |
| 75 | void DbusLoop::addIoHandler(int fd, std::function<void()> callback) |
| 76 | { |
| 77 | ioHandler = callback; |
| 78 | const int rc = sd_event_add_io(event, nullptr, fd, EPOLLIN, |
| 79 | &DbusLoop::ioCallback, this); |
| 80 | if (rc < 0) |
| 81 | { |
| 82 | std::error_code ec(-rc, std::generic_category()); |
| 83 | throw std::system_error(ec, "Unable to register IO handler"); |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | void DbusLoop::addSignalHandler(int signal, std::function<void()> callback) |
| 88 | { |
| 89 | // Block the signal |
| 90 | sigset_t ss; |
| 91 | if (sigemptyset(&ss) < 0 || sigaddset(&ss, signal) < 0 || |
| 92 | sigprocmask(SIG_BLOCK, &ss, nullptr) < 0) |
| 93 | { |
| 94 | std::error_code ec(errno, std::generic_category()); |
| 95 | std::string err = "Unable to block signal "; |
| 96 | err += strsignal(signal); |
| 97 | throw std::system_error(ec, err); |
| 98 | } |
| 99 | |
| 100 | signalHandlers.insert(std::make_pair(signal, callback)); |
| 101 | |
| 102 | // Register handler |
| 103 | const int rc = sd_event_add_signal(event, nullptr, signal, |
| 104 | &DbusLoop::signalCallback, this); |
| 105 | if (rc < 0) |
| 106 | { |
| 107 | std::error_code ec(-rc, std::generic_category()); |
| 108 | std::string err = "Unable to register handler for signal "; |
| 109 | err += strsignal(signal); |
| 110 | throw std::system_error(ec, err); |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | int DbusLoop::msgCallback(sd_bus_message* msg, void* userdata, |
| 115 | sd_bus_error* /*err*/) |
| 116 | { |
| 117 | const WatchProperties& propWatch = |
| 118 | static_cast<DbusLoop*>(userdata)->propWatch; |
| 119 | |
| 120 | try |
| 121 | { |
| 122 | int rc; |
| 123 | |
| 124 | // Filter out by interface name |
| 125 | const char* interface; |
| 126 | rc = sd_bus_message_read(msg, "s", &interface); |
| 127 | if (rc < 0) |
| 128 | { |
| 129 | std::error_code ec(-rc, std::generic_category()); |
| 130 | throw std::system_error(ec, "Unable to read interface name"); |
| 131 | } |
| 132 | const auto& itIface = propWatch.find(interface); |
| 133 | if (itIface == propWatch.end()) |
| 134 | { |
| 135 | return 0; // Interface is now watched |
| 136 | } |
| 137 | const Properties& props = itIface->second; |
| 138 | |
| 139 | // Read message: go through list of changed properties |
| 140 | rc = sd_bus_message_enter_container(msg, SD_BUS_TYPE_ARRAY, "{sv}"); |
| 141 | if (rc < 0) |
| 142 | { |
| 143 | std::error_code ec(-rc, std::generic_category()); |
| 144 | throw std::system_error(ec, "Unable to open message container"); |
| 145 | } |
| 146 | while ((rc = sd_bus_message_enter_container(msg, SD_BUS_TYPE_DICT_ENTRY, |
| 147 | "sv")) > 0) |
| 148 | { |
| 149 | // Get property's name |
| 150 | const char* name; |
| 151 | rc = sd_bus_message_read(msg, "s", &name); |
| 152 | if (rc < 0) |
| 153 | { |
| 154 | sd_bus_message_exit_container(msg); |
| 155 | std::error_code ec(-rc, std::generic_category()); |
| 156 | throw std::system_error(ec, "Unable to get property name"); |
| 157 | } |
| 158 | |
| 159 | // Get and check property's type |
| 160 | const char* type; |
| 161 | rc = sd_bus_message_peek_type(msg, nullptr, &type); |
| 162 | if (rc < 0 || strcmp(type, "s")) |
| 163 | { |
| 164 | sd_bus_message_exit_container(msg); |
| 165 | continue; |
| 166 | } |
| 167 | |
| 168 | // Get property's value |
| 169 | const char* value; |
| 170 | rc = sd_bus_message_enter_container(msg, SD_BUS_TYPE_VARIANT, type); |
| 171 | if (rc < 0) |
| 172 | { |
| 173 | sd_bus_message_exit_container(msg); |
| 174 | std::error_code ec(-rc, std::generic_category()); |
| 175 | throw std::system_error(ec, "Unable to open property value"); |
| 176 | } |
| 177 | rc = sd_bus_message_read(msg, type, &value); |
| 178 | if (rc < 0) |
| 179 | { |
| 180 | sd_bus_message_exit_container(msg); |
| 181 | sd_bus_message_exit_container(msg); |
| 182 | std::error_code ec(-rc, std::generic_category()); |
| 183 | throw std::system_error(ec, "Unable to get property value"); |
| 184 | } |
| 185 | sd_bus_message_exit_container(msg); |
| 186 | |
| 187 | // Check property name/value and handle the match |
| 188 | const auto& itProps = props.find(name); |
| 189 | if (itProps != props.end() && |
| 190 | itProps->second.find(value) != itProps->second.end()) |
| 191 | { |
| 192 | static_cast<DbusLoop*>(userdata)->propHandler(); |
| 193 | } |
| 194 | |
| 195 | sd_bus_message_exit_container(msg); |
| 196 | } |
| 197 | sd_bus_message_exit_container(msg); |
| 198 | } |
| 199 | catch (const std::exception& ex) |
| 200 | { |
| 201 | log<level::WARNING>(ex.what()); |
| 202 | } |
| 203 | |
| 204 | return 0; |
| 205 | } |
| 206 | |
| 207 | int DbusLoop::signalCallback(sd_event_source* /*src*/, |
| 208 | const struct signalfd_siginfo* si, void* userdata) |
| 209 | { |
| 210 | DbusLoop* instance = static_cast<DbusLoop*>(userdata); |
| 211 | const auto it = instance->signalHandlers.find(si->ssi_signo); |
| 212 | if (it != instance->signalHandlers.end()) |
| 213 | { |
| 214 | it->second(); |
| 215 | } |
| 216 | else |
| 217 | { |
| 218 | std::string msg = "Unhandled signal "; |
| 219 | msg += strsignal(si->ssi_signo); |
| 220 | log<level::WARNING>(msg.c_str()); |
| 221 | } |
| 222 | return 0; |
| 223 | } |
| 224 | |
| 225 | int DbusLoop::ioCallback(sd_event_source* /*src*/, int /*fd*/, |
| 226 | uint32_t /*revents*/, void* userdata) |
| 227 | { |
| 228 | static_cast<DbusLoop*>(userdata)->ioHandler(); |
| 229 | return 0; |
| 230 | } |