#pragma once #include <phosphor-logging/elog-errors.hpp> #include <phosphor-logging/elog.hpp> #include <phosphor-logging/log.hpp> #include <sdbusplus/bus.hpp> #include <sdbusplus/bus/match.hpp> #include <sdbusplus/message.hpp> #include <xyz/openbmc_project/Common/error.hpp> #include <format> namespace phosphor { namespace fan { namespace util { namespace detail { namespace errors = sdbusplus::xyz::openbmc_project::Common::Error; } // namespace detail /** * @class DBusError * * The base class for the exceptions thrown on fails in the various * SDBusPlus calls. Used so that a single catch statement can catch * any type of these exceptions. * * None of these exceptions will log anything when they are created, * it is up to the handler to do that if desired. */ class DBusError : public std::runtime_error { public: explicit DBusError(const std::string& msg) : std::runtime_error(msg) {} }; /** * @class DBusMethodError * * Thrown on a DBus Method call failure */ class DBusMethodError : public DBusError { public: DBusMethodError(const std::string& busName, const std::string& path, const std::string& interface, const std::string& method) : DBusError(std::format("DBus method failed: {} {} {} {}", busName, path, interface, method)), busName(busName), path(path), interface(interface), method(method) {} const std::string busName; const std::string path; const std::string interface; const std::string method; }; /** * @class DBusServiceError * * Thrown when a service lookup fails. Usually this points to * the object path not being present in D-Bus. */ class DBusServiceError : public DBusError { public: DBusServiceError(const std::string& path, const std::string& interface) : DBusError( std::format("DBus service lookup failed: {} {}", path, interface)), path(path), interface(interface) {} const std::string path; const std::string interface; }; /** * @class DBusPropertyError * * Thrown when a set/get property fails. */ class DBusPropertyError : public DBusError { public: DBusPropertyError(const std::string& msg, const std::string& busName, const std::string& path, const std::string& interface, const std::string& property) : DBusError(msg + std::format(": {} {} {} {}", busName, path, interface, property)), busName(busName), path(path), interface(interface), property(property) {} const std::string busName; const std::string path; const std::string interface; const std::string property; }; /** @brief Alias for PropertiesChanged signal callbacks. */ template <typename... T> using Properties = std::map<std::string, std::variant<T...>>; /** @class SDBusPlus * @brief DBus access delegate implementation for sdbusplus. */ class SDBusPlus { public: /** @brief Get the bus connection. */ static auto& getBus() __attribute__((pure)) { static auto bus = sdbusplus::bus::new_default(); return bus; } /** @brief Invoke a method. */ template <typename... Args> static auto callMethod(sdbusplus::bus_t& bus, const std::string& busName, const std::string& path, const std::string& interface, const std::string& method, Args&&... args) { auto reqMsg = bus.new_method_call(busName.c_str(), path.c_str(), interface.c_str(), method.c_str()); reqMsg.append(std::forward<Args>(args)...); try { auto respMsg = bus.call(reqMsg); if (respMsg.is_method_error()) { throw DBusMethodError{busName, path, interface, method}; } return respMsg; } catch (const sdbusplus::exception_t&) { throw DBusMethodError{busName, path, interface, method}; } } /** @brief Invoke a method. */ template <typename... Args> static auto callMethod(const std::string& busName, const std::string& path, const std::string& interface, const std::string& method, Args&&... args) { return callMethod(getBus(), busName, path, interface, method, std::forward<Args>(args)...); } /** @brief Invoke a method and read the response. */ template <typename Ret, typename... Args> static auto callMethodAndRead(sdbusplus::bus_t& bus, const std::string& busName, const std::string& path, const std::string& interface, const std::string& method, Args&&... args) { sdbusplus::message_t respMsg = callMethod<Args...>( bus, busName, path, interface, method, std::forward<Args>(args)...); Ret resp; respMsg.read(resp); return resp; } /** @brief Invoke a method and read the response. */ template <typename Ret, typename... Args> static auto callMethodAndRead( const std::string& busName, const std::string& path, const std::string& interface, const std::string& method, Args&&... args) { return callMethodAndRead<Ret>(getBus(), busName, path, interface, method, std::forward<Args>(args)...); } /** @brief Get subtree from the mapper without checking response. */ static auto getSubTreeRaw(sdbusplus::bus_t& bus, const std::string& path, const std::string& interface, int32_t depth) { using namespace std::literals::string_literals; using Path = std::string; using Intf = std::string; using Serv = std::string; using Intfs = std::vector<Intf>; using Objects = std::map<Path, std::map<Serv, Intfs>>; Intfs intfs = {interface}; return callMethodAndRead<Objects>( bus, "xyz.openbmc_project.ObjectMapper"s, "/xyz/openbmc_project/object_mapper"s, "xyz.openbmc_project.ObjectMapper"s, "GetSubTree"s, path, depth, intfs); } /** @brief Get subtree from the mapper without checking response, * (multiple interfaces version). */ static auto getSubTreeRaw(sdbusplus::bus_t& bus, const std::string& path, const std::vector<std::string>& intfs, int32_t depth) { using namespace std::literals::string_literals; using Path = std::string; using Intf = std::string; using Serv = std::string; using Intfs = std::vector<Intf>; using Objects = std::map<Path, std::map<Serv, Intfs>>; return callMethodAndRead<Objects>( bus, "xyz.openbmc_project.ObjectMapper"s, "/xyz/openbmc_project/object_mapper"s, "xyz.openbmc_project.ObjectMapper"s, "GetSubTree"s, path, depth, intfs); } /** @brief Get subtree from the mapper. */ static auto getSubTree(sdbusplus::bus_t& bus, const std::string& path, const std::string& interface, int32_t depth) { auto mapperResp = getSubTreeRaw(bus, path, interface, depth); if (mapperResp.empty()) { phosphor::logging::log<phosphor::logging::level::ERR>( "Empty response from mapper GetSubTree", phosphor::logging::entry("SUBTREE=%s", path.c_str()), phosphor::logging::entry("INTERFACE=%s", interface.c_str()), phosphor::logging::entry("DEPTH=%u", depth)); phosphor::logging::elog<detail::errors::InternalFailure>(); } return mapperResp; } /** @brief Get subtree paths from the mapper without checking response. */ static auto getSubTreePathsRaw(sdbusplus::bus_t& bus, const std::string& path, const std::string& interface, int32_t depth) { using namespace std::literals::string_literals; using Path = std::string; using Intf = std::string; using Intfs = std::vector<Intf>; using ObjectPaths = std::vector<Path>; Intfs intfs = {interface}; return callMethodAndRead<ObjectPaths>( bus, "xyz.openbmc_project.ObjectMapper"s, "/xyz/openbmc_project/object_mapper"s, "xyz.openbmc_project.ObjectMapper"s, "GetSubTreePaths"s, path, depth, intfs); } /** @brief Get subtree paths from the mapper. */ static auto getSubTreePaths(sdbusplus::bus_t& bus, const std::string& path, const std::string& interface, int32_t depth) { auto mapperResp = getSubTreePathsRaw(bus, path, interface, depth); if (mapperResp.empty()) { phosphor::logging::log<phosphor::logging::level::ERR>( "Empty response from mapper GetSubTreePaths", phosphor::logging::entry("SUBTREE=%s", path.c_str()), phosphor::logging::entry("INTERFACE=%s", interface.c_str()), phosphor::logging::entry("DEPTH=%u", depth)); phosphor::logging::elog<detail::errors::InternalFailure>(); } return mapperResp; } /** @brief Get service from the mapper without checking response. */ static auto getServiceRaw(sdbusplus::bus_t& bus, const std::string& path, const std::string& interface) { using namespace std::literals::string_literals; using GetObject = std::map<std::string, std::vector<std::string>>; return callMethodAndRead<GetObject>( bus, "xyz.openbmc_project.ObjectMapper"s, "/xyz/openbmc_project/object_mapper"s, "xyz.openbmc_project.ObjectMapper"s, "GetObject"s, path, GetObject::mapped_type{interface}); } /** @brief Get service from the mapper. */ static auto getService(sdbusplus::bus_t& bus, const std::string& path, const std::string& interface) { try { auto mapperResp = getServiceRaw(bus, path, interface); if (mapperResp.empty()) { // Should never happen. A missing object would fail // in callMethodAndRead() phosphor::logging::log<phosphor::logging::level::ERR>( "Empty mapper response on service lookup"); throw DBusServiceError{path, interface}; } return mapperResp.begin()->first; } catch (const DBusMethodError& e) { throw DBusServiceError{path, interface}; } } /** @brief Get service from the mapper. */ static auto getService(const std::string& path, const std::string& interface) { return getService(getBus(), path, interface); } /** @brief Get managed objects. */ template <typename Variant> static auto getManagedObjects(sdbusplus::bus_t& bus, const std::string& service, const std::string& path) { using namespace std::literals::string_literals; using Path = sdbusplus::message::object_path; using Intf = std::string; using Prop = std::string; using GetManagedObjects = std::map<Path, std::map<Intf, std::map<Prop, Variant>>>; return callMethodAndRead<GetManagedObjects>( bus, service, path, "org.freedesktop.DBus.ObjectManager"s, "GetManagedObjects"s); } /** @brief Get a property with mapper lookup. */ template <typename Property> static auto getProperty(sdbusplus::bus_t& bus, const std::string& path, const std::string& interface, const std::string& property) { using namespace std::literals::string_literals; auto service = getService(bus, path, interface); auto msg = callMethod(bus, service, path, "org.freedesktop.DBus.Properties"s, "Get"s, interface, property); if (msg.is_method_error()) { throw DBusPropertyError{"DBus get property failed", service, path, interface, property}; } std::variant<Property> value; msg.read(value); return std::get<Property>(value); } /** @brief Get a property with mapper lookup. */ template <typename Property> static auto getProperty(const std::string& path, const std::string& interface, const std::string& property) { return getProperty<Property>(getBus(), path, interface, property); } /** @brief Get a property variant with mapper lookup. */ template <typename Variant> static auto getPropertyVariant( sdbusplus::bus_t& bus, const std::string& path, const std::string& interface, const std::string& property) { using namespace std::literals::string_literals; auto service = getService(bus, path, interface); auto msg = callMethod(bus, service, path, "org.freedesktop.DBus.Properties"s, "Get"s, interface, property); if (msg.is_method_error()) { throw DBusPropertyError{"DBus get property variant failed", service, path, interface, property}; } Variant value; msg.read(value); return value; } /** @brief Get a property variant with mapper lookup. */ template <typename Variant> static auto getPropertyVariant(const std::string& path, const std::string& interface, const std::string& property) { return getPropertyVariant<Variant>(getBus(), path, interface, property); } /** @brief Invoke a method and return without checking for error. */ template <typename... Args> static auto callMethodAndReturn( sdbusplus::bus_t& bus, const std::string& busName, const std::string& path, const std::string& interface, const std::string& method, Args&&... args) { auto reqMsg = bus.new_method_call(busName.c_str(), path.c_str(), interface.c_str(), method.c_str()); reqMsg.append(std::forward<Args>(args)...); auto respMsg = bus.call(reqMsg); return respMsg; } /** @brief Get a property without mapper lookup. */ template <typename Property> static auto getProperty(sdbusplus::bus_t& bus, const std::string& service, const std::string& path, const std::string& interface, const std::string& property) { using namespace std::literals::string_literals; auto msg = callMethodAndReturn(bus, service, path, "org.freedesktop.DBus.Properties"s, "Get"s, interface, property); if (msg.is_method_error()) { throw DBusPropertyError{"DBus get property failed", service, path, interface, property}; } std::variant<Property> value; msg.read(value); return std::get<Property>(value); } /** @brief Get a property without mapper lookup. */ template <typename Property> static auto getProperty(const std::string& service, const std::string& path, const std::string& interface, const std::string& property) { return getProperty<Property>(getBus(), service, path, interface, property); } /** @brief Get a property variant without mapper lookup. */ template <typename Variant> static auto getPropertyVariant( sdbusplus::bus_t& bus, const std::string& service, const std::string& path, const std::string& interface, const std::string& property) { using namespace std::literals::string_literals; auto msg = callMethodAndReturn(bus, service, path, "org.freedesktop.DBus.Properties"s, "Get"s, interface, property); if (msg.is_method_error()) { throw DBusPropertyError{"DBus get property variant failed", service, path, interface, property}; } Variant value; msg.read(value); return value; } /** @brief Get a property variant without mapper lookup. */ template <typename Variant> static auto getPropertyVariant( const std::string& service, const std::string& path, const std::string& interface, const std::string& property) { return getPropertyVariant<Variant>(getBus(), service, path, interface, property); } /** @brief Set a property with mapper lookup. */ template <typename Property> static void setProperty(sdbusplus::bus_t& bus, const std::string& path, const std::string& interface, const std::string& property, Property&& value) { using namespace std::literals::string_literals; std::variant<Property> varValue(std::forward<Property>(value)); auto service = getService(bus, path, interface); auto msg = callMethodAndReturn(bus, service, path, "org.freedesktop.DBus.Properties"s, "Set"s, interface, property, varValue); if (msg.is_method_error()) { throw DBusPropertyError{"DBus set property failed", service, path, interface, property}; } } /** @brief Set a property with mapper lookup. */ template <typename Property> static void setProperty(const std::string& path, const std::string& interface, const std::string& property, Property&& value) { return setProperty(getBus(), path, interface, property, std::forward<Property>(value)); } /** @brief Set a property without mapper lookup. */ template <typename Property> static void setProperty(sdbusplus::bus_t& bus, const std::string& service, const std::string& path, const std::string& interface, const std::string& property, Property&& value) { using namespace std::literals::string_literals; std::variant<Property> varValue(std::forward<Property>(value)); auto msg = callMethodAndReturn(bus, service, path, "org.freedesktop.DBus.Properties"s, "Set"s, interface, property, varValue); if (msg.is_method_error()) { throw DBusPropertyError{"DBus set property failed", service, path, interface, property}; } } /** @brief Set a property without mapper lookup. */ template <typename Property> static void setProperty(const std::string& service, const std::string& path, const std::string& interface, const std::string& property, Property&& value) { return setProperty(getBus(), service, path, interface, property, std::forward<Property>(value)); } /** @brief Invoke method with mapper lookup. */ template <typename... Args> static auto lookupAndCallMethod( sdbusplus::bus_t& bus, const std::string& path, const std::string& interface, const std::string& method, Args&&... args) { return callMethod(bus, getService(bus, path, interface), path, interface, method, std::forward<Args>(args)...); } /** @brief Invoke method with mapper lookup. */ template <typename... Args> static auto lookupAndCallMethod(const std::string& path, const std::string& interface, const std::string& method, Args&&... args) { return lookupAndCallMethod(getBus(), path, interface, method, std::forward<Args>(args)...); } /** @brief Invoke method and read with mapper lookup. */ template <typename Ret, typename... Args> static auto lookupCallMethodAndRead( sdbusplus::bus_t& bus, const std::string& path, const std::string& interface, const std::string& method, Args&&... args) { return callMethodAndRead(bus, getService(bus, path, interface), path, interface, method, std::forward<Args>(args)...); } /** @brief Invoke method and read with mapper lookup. */ template <typename Ret, typename... Args> static auto lookupCallMethodAndRead( const std::string& path, const std::string& interface, const std::string& method, Args&&... args) { return lookupCallMethodAndRead<Ret>(getBus(), path, interface, method, std::forward<Args>(args)...); } }; } // namespace util } // namespace fan } // namespace phosphor