| #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 |