xref: /openbmc/phosphor-fan-presence/sdbusplus.hpp (revision 32c4feff9b1cd1fd4d75bb530761f70f0e5922e0)
1 #pragma once
2 
3 #include <phosphor-logging/elog-errors.hpp>
4 #include <phosphor-logging/elog.hpp>
5 #include <phosphor-logging/lg2.hpp>
6 #include <sdbusplus/bus.hpp>
7 #include <sdbusplus/bus/match.hpp>
8 #include <sdbusplus/message.hpp>
9 #include <xyz/openbmc_project/Common/error.hpp>
10 
11 #include <format>
12 
13 namespace phosphor
14 {
15 namespace fan
16 {
17 namespace util
18 {
19 namespace detail
20 {
21 namespace errors = sdbusplus::xyz::openbmc_project::Common::Error;
22 } // namespace detail
23 
24 /**
25  * @class DBusError
26  *
27  * The base class for the exceptions thrown on fails in the various
28  * SDBusPlus calls.  Used so that a single catch statement can catch
29  * any type of these exceptions.
30  *
31  * None of these exceptions will log anything when they are created,
32  * it is up to the handler to do that if desired.
33  */
34 class DBusError : public std::runtime_error
35 {
36   public:
DBusError(const std::string & msg)37     explicit DBusError(const std::string& msg) : std::runtime_error(msg) {}
38 };
39 
40 /**
41  * @class DBusMethodError
42  *
43  * Thrown on a DBus Method call failure
44  */
45 class DBusMethodError : public DBusError
46 {
47   public:
DBusMethodError(const std::string & busName,const std::string & path,const std::string & interface,const std::string & method)48     DBusMethodError(const std::string& busName, const std::string& path,
49                     const std::string& interface, const std::string& method) :
50         DBusError(std::format("DBus method failed: {} {} {} {}", busName, path,
51                               interface, method)),
52         busName(busName), path(path), interface(interface), method(method)
53     {}
54 
55     const std::string busName;
56     const std::string path;
57     const std::string interface;
58     const std::string method;
59 };
60 
61 /**
62  * @class DBusServiceError
63  *
64  * Thrown when a service lookup fails.  Usually this points to
65  * the object path not being present in D-Bus.
66  */
67 class DBusServiceError : public DBusError
68 {
69   public:
DBusServiceError(const std::string & path,const std::string & interface)70     DBusServiceError(const std::string& path, const std::string& interface) :
71         DBusError(
72             std::format("DBus service lookup failed: {} {}", path, interface)),
73         path(path), interface(interface)
74     {}
75 
76     const std::string path;
77     const std::string interface;
78 };
79 
80 /**
81  * @class DBusPropertyError
82  *
83  * Thrown when a set/get property fails.
84  */
85 class DBusPropertyError : public DBusError
86 {
87   public:
DBusPropertyError(const std::string & msg,const std::string & busName,const std::string & path,const std::string & interface,const std::string & property)88     DBusPropertyError(const std::string& msg, const std::string& busName,
89                       const std::string& path, const std::string& interface,
90                       const std::string& property) :
91         DBusError(msg + std::format(": {} {} {} {}", busName, path, interface,
92                                     property)),
93         busName(busName), path(path), interface(interface), property(property)
94     {}
95 
96     const std::string busName;
97     const std::string path;
98     const std::string interface;
99     const std::string property;
100 };
101 
102 /** @brief Alias for PropertiesChanged signal callbacks. */
103 template <typename... T>
104 using Properties = std::map<std::string, std::variant<T...>>;
105 
106 /** @class SDBusPlus
107  *  @brief DBus access delegate implementation for sdbusplus.
108  */
109 class SDBusPlus
110 {
111   public:
112     /** @brief Get the bus connection. */
getBus()113     static auto& getBus() __attribute__((pure))
114     {
115         static auto bus = sdbusplus::bus::new_default();
116         return bus;
117     }
118 
119     /** @brief Invoke a method. */
120     template <typename... Args>
callMethod(sdbusplus::bus_t & bus,const std::string & busName,const std::string & path,const std::string & interface,const std::string & method,Args &&...args)121     static auto callMethod(sdbusplus::bus_t& bus, const std::string& busName,
122                            const std::string& path,
123                            const std::string& interface,
124                            const std::string& method, Args&&... args)
125     {
126         auto reqMsg = bus.new_method_call(busName.c_str(), path.c_str(),
127                                           interface.c_str(), method.c_str());
128         reqMsg.append(std::forward<Args>(args)...);
129         try
130         {
131             auto respMsg = bus.call(reqMsg);
132             if (respMsg.is_method_error())
133             {
134                 throw DBusMethodError{busName, path, interface, method};
135             }
136             return respMsg;
137         }
138         catch (const sdbusplus::exception_t&)
139         {
140             throw DBusMethodError{busName, path, interface, method};
141         }
142     }
143 
144     /** @brief Invoke a method. */
145     template <typename... Args>
callMethod(const std::string & busName,const std::string & path,const std::string & interface,const std::string & method,Args &&...args)146     static auto callMethod(const std::string& busName, const std::string& path,
147                            const std::string& interface,
148                            const std::string& method, Args&&... args)
149     {
150         return callMethod(getBus(), busName, path, interface, method,
151                           std::forward<Args>(args)...);
152     }
153 
154     /** @brief Invoke a method and read the response. */
155     template <typename Ret, typename... Args>
callMethodAndRead(sdbusplus::bus_t & bus,const std::string & busName,const std::string & path,const std::string & interface,const std::string & method,Args &&...args)156     static auto callMethodAndRead(
157         sdbusplus::bus_t& bus, const std::string& busName,
158         const std::string& path, const std::string& interface,
159         const std::string& method, Args&&... args)
160     {
161         sdbusplus::message_t respMsg = callMethod<Args...>(
162             bus, busName, path, interface, method, std::forward<Args>(args)...);
163         Ret resp;
164         respMsg.read(resp);
165         return resp;
166     }
167 
168     /** @brief Invoke a method and read the response. */
169     template <typename Ret, typename... Args>
callMethodAndRead(const std::string & busName,const std::string & path,const std::string & interface,const std::string & method,Args &&...args)170     static auto callMethodAndRead(
171         const std::string& busName, const std::string& path,
172         const std::string& interface, const std::string& method, Args&&... args)
173     {
174         return callMethodAndRead<Ret>(getBus(), busName, path, interface,
175                                       method, std::forward<Args>(args)...);
176     }
177 
178     /** @brief Get subtree from the mapper without checking response. */
getSubTreeRaw(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface,int32_t depth)179     static auto getSubTreeRaw(sdbusplus::bus_t& bus, const std::string& path,
180                               const std::string& interface, int32_t depth)
181     {
182         using namespace std::literals::string_literals;
183 
184         using Path = std::string;
185         using Intf = std::string;
186         using Serv = std::string;
187         using Intfs = std::vector<Intf>;
188         using Objects = std::map<Path, std::map<Serv, Intfs>>;
189         Intfs intfs = {interface};
190 
191         return callMethodAndRead<Objects>(
192             bus, "xyz.openbmc_project.ObjectMapper"s,
193             "/xyz/openbmc_project/object_mapper"s,
194             "xyz.openbmc_project.ObjectMapper"s, "GetSubTree"s, path, depth,
195             intfs);
196     }
197 
198     /** @brief Get subtree from the mapper without checking response,
199      * (multiple interfaces version). */
getSubTreeRaw(sdbusplus::bus_t & bus,const std::string & path,const std::vector<std::string> & intfs,int32_t depth)200     static auto getSubTreeRaw(sdbusplus::bus_t& bus, const std::string& path,
201                               const std::vector<std::string>& intfs,
202                               int32_t depth)
203     {
204         using namespace std::literals::string_literals;
205 
206         using Path = std::string;
207         using Intf = std::string;
208         using Serv = std::string;
209         using Intfs = std::vector<Intf>;
210         using Objects = std::map<Path, std::map<Serv, Intfs>>;
211 
212         return callMethodAndRead<Objects>(
213             bus, "xyz.openbmc_project.ObjectMapper"s,
214             "/xyz/openbmc_project/object_mapper"s,
215             "xyz.openbmc_project.ObjectMapper"s, "GetSubTree"s, path, depth,
216             intfs);
217     }
218 
219     /** @brief Get subtree from the mapper. */
getSubTree(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface,int32_t depth)220     static auto getSubTree(sdbusplus::bus_t& bus, const std::string& path,
221                            const std::string& interface, int32_t depth)
222     {
223         auto mapperResp = getSubTreeRaw(bus, path, interface, depth);
224         if (mapperResp.empty())
225         {
226             lg2::error(
227                 "Empty response from mapper GetSubTree, SubTree={SUBTREE}, Interface={INTERFACE}, Depth={DEPTH}",
228                 "SUBTREE", path, "INTERFACE", interface, "DEPTH", depth);
229             phosphor::logging::elog<detail::errors::InternalFailure>();
230         }
231         return mapperResp;
232     }
233 
234     /** @brief Get subtree paths from the mapper without checking response. */
getSubTreePathsRaw(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface,int32_t depth)235     static auto getSubTreePathsRaw(sdbusplus::bus_t& bus,
236                                    const std::string& path,
237                                    const std::string& interface, int32_t depth)
238     {
239         using namespace std::literals::string_literals;
240 
241         using Path = std::string;
242         using Intf = std::string;
243         using Intfs = std::vector<Intf>;
244         using ObjectPaths = std::vector<Path>;
245         Intfs intfs = {interface};
246 
247         return callMethodAndRead<ObjectPaths>(
248             bus, "xyz.openbmc_project.ObjectMapper"s,
249             "/xyz/openbmc_project/object_mapper"s,
250             "xyz.openbmc_project.ObjectMapper"s, "GetSubTreePaths"s, path,
251             depth, intfs);
252     }
253 
254     /** @brief Get subtree paths from the mapper. */
getSubTreePaths(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface,int32_t depth)255     static auto getSubTreePaths(sdbusplus::bus_t& bus, const std::string& path,
256                                 const std::string& interface, int32_t depth)
257     {
258         auto mapperResp = getSubTreePathsRaw(bus, path, interface, depth);
259         if (mapperResp.empty())
260         {
261             lg2::error(
262                 "Empty response from mapper GetSubTreePaths, SubTree={SUBTREE}, Interface={INTERFACE}, Depth={DEPTH}",
263                 "SUBTREE", path, "INTERFACE", interface, "DEPTH", depth);
264             phosphor::logging::elog<detail::errors::InternalFailure>();
265         }
266         return mapperResp;
267     }
268 
269     /** @brief Get service from the mapper without checking response. */
getServiceRaw(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface)270     static auto getServiceRaw(sdbusplus::bus_t& bus, const std::string& path,
271                               const std::string& interface)
272     {
273         using namespace std::literals::string_literals;
274         using GetObject = std::map<std::string, std::vector<std::string>>;
275 
276         return callMethodAndRead<GetObject>(
277             bus, "xyz.openbmc_project.ObjectMapper"s,
278             "/xyz/openbmc_project/object_mapper"s,
279             "xyz.openbmc_project.ObjectMapper"s, "GetObject"s, path,
280             GetObject::mapped_type{interface});
281     }
282 
283     /** @brief Get service from the mapper. */
getService(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface)284     static auto getService(sdbusplus::bus_t& bus, const std::string& path,
285                            const std::string& interface)
286     {
287         try
288         {
289             auto mapperResp = getServiceRaw(bus, path, interface);
290 
291             if (mapperResp.empty())
292             {
293                 // Should never happen.  A missing object would fail
294                 // in callMethodAndRead()
295                 lg2::error("Empty mapper response on service lookup");
296                 throw DBusServiceError{path, interface};
297             }
298             return mapperResp.begin()->first;
299         }
300         catch (const DBusMethodError& e)
301         {
302             throw DBusServiceError{path, interface};
303         }
304     }
305 
306     /** @brief Get service from the mapper. */
getService(const std::string & path,const std::string & interface)307     static auto getService(const std::string& path,
308                            const std::string& interface)
309     {
310         return getService(getBus(), path, interface);
311     }
312 
313     /** @brief Get managed objects. */
314     template <typename Variant>
getManagedObjects(sdbusplus::bus_t & bus,const std::string & service,const std::string & path)315     static auto getManagedObjects(sdbusplus::bus_t& bus,
316                                   const std::string& service,
317                                   const std::string& path)
318     {
319         using namespace std::literals::string_literals;
320 
321         using Path = sdbusplus::message::object_path;
322         using Intf = std::string;
323         using Prop = std::string;
324         using GetManagedObjects =
325             std::map<Path, std::map<Intf, std::map<Prop, Variant>>>;
326 
327         return callMethodAndRead<GetManagedObjects>(
328             bus, service, path, "org.freedesktop.DBus.ObjectManager"s,
329             "GetManagedObjects"s);
330     }
331 
332     /** @brief Get a property with mapper lookup. */
333     template <typename Property>
getProperty(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface,const std::string & property)334     static auto getProperty(sdbusplus::bus_t& bus, const std::string& path,
335                             const std::string& interface,
336                             const std::string& property)
337     {
338         using namespace std::literals::string_literals;
339 
340         auto service = getService(bus, path, interface);
341         auto msg =
342             callMethod(bus, service, path, "org.freedesktop.DBus.Properties"s,
343                        "Get"s, interface, property);
344         if (msg.is_method_error())
345         {
346             throw DBusPropertyError{"DBus get property failed", service, path,
347                                     interface, property};
348         }
349         std::variant<Property> value;
350         msg.read(value);
351         return std::get<Property>(value);
352     }
353 
354     /** @brief Get a property with mapper lookup. */
355     template <typename Property>
getProperty(const std::string & path,const std::string & interface,const std::string & property)356     static auto getProperty(const std::string& path,
357                             const std::string& interface,
358                             const std::string& property)
359     {
360         return getProperty<Property>(getBus(), path, interface, property);
361     }
362 
363     /** @brief Get a property variant with mapper lookup. */
364     template <typename Variant>
getPropertyVariant(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface,const std::string & property)365     static auto getPropertyVariant(
366         sdbusplus::bus_t& bus, const std::string& path,
367         const std::string& interface, const std::string& property)
368     {
369         using namespace std::literals::string_literals;
370 
371         auto service = getService(bus, path, interface);
372         auto msg =
373             callMethod(bus, service, path, "org.freedesktop.DBus.Properties"s,
374                        "Get"s, interface, property);
375         if (msg.is_method_error())
376         {
377             throw DBusPropertyError{"DBus get property variant failed", service,
378                                     path, interface, property};
379         }
380         Variant value;
381         msg.read(value);
382         return value;
383     }
384 
385     /** @brief Get a property variant with mapper lookup. */
386     template <typename Variant>
getPropertyVariant(const std::string & path,const std::string & interface,const std::string & property)387     static auto getPropertyVariant(const std::string& path,
388                                    const std::string& interface,
389                                    const std::string& property)
390     {
391         return getPropertyVariant<Variant>(getBus(), path, interface, property);
392     }
393 
394     /** @brief Invoke a method and return without checking for error. */
395     template <typename... Args>
callMethodAndReturn(sdbusplus::bus_t & bus,const std::string & busName,const std::string & path,const std::string & interface,const std::string & method,Args &&...args)396     static auto callMethodAndReturn(
397         sdbusplus::bus_t& bus, const std::string& busName,
398         const std::string& path, const std::string& interface,
399         const std::string& method, Args&&... args)
400     {
401         auto reqMsg = bus.new_method_call(busName.c_str(), path.c_str(),
402                                           interface.c_str(), method.c_str());
403         reqMsg.append(std::forward<Args>(args)...);
404         auto respMsg = bus.call(reqMsg);
405 
406         return respMsg;
407     }
408 
409     /** @brief Get a property without mapper lookup. */
410     template <typename Property>
getProperty(sdbusplus::bus_t & bus,const std::string & service,const std::string & path,const std::string & interface,const std::string & property)411     static auto getProperty(sdbusplus::bus_t& bus, const std::string& service,
412                             const std::string& path,
413                             const std::string& interface,
414                             const std::string& property)
415     {
416         using namespace std::literals::string_literals;
417 
418         auto msg = callMethodAndReturn(bus, service, path,
419                                        "org.freedesktop.DBus.Properties"s,
420                                        "Get"s, interface, property);
421         if (msg.is_method_error())
422         {
423             throw DBusPropertyError{"DBus get property failed", service, path,
424                                     interface, property};
425         }
426         std::variant<Property> value;
427         msg.read(value);
428         return std::get<Property>(value);
429     }
430 
431     /** @brief Get a property without mapper lookup. */
432     template <typename Property>
getProperty(const std::string & service,const std::string & path,const std::string & interface,const std::string & property)433     static auto getProperty(const std::string& service, const std::string& path,
434                             const std::string& interface,
435                             const std::string& property)
436     {
437         return getProperty<Property>(getBus(), service, path, interface,
438                                      property);
439     }
440 
441     /** @brief Get a property variant without mapper lookup. */
442     template <typename Variant>
getPropertyVariant(sdbusplus::bus_t & bus,const std::string & service,const std::string & path,const std::string & interface,const std::string & property)443     static auto getPropertyVariant(
444         sdbusplus::bus_t& bus, const std::string& service,
445         const std::string& path, const std::string& interface,
446         const std::string& property)
447     {
448         using namespace std::literals::string_literals;
449 
450         auto msg = callMethodAndReturn(bus, service, path,
451                                        "org.freedesktop.DBus.Properties"s,
452                                        "Get"s, interface, property);
453         if (msg.is_method_error())
454         {
455             throw DBusPropertyError{"DBus get property variant failed", service,
456                                     path, interface, property};
457         }
458         Variant value;
459         msg.read(value);
460         return value;
461     }
462 
463     /** @brief Get a property variant without mapper lookup. */
464     template <typename Variant>
getPropertyVariant(const std::string & service,const std::string & path,const std::string & interface,const std::string & property)465     static auto getPropertyVariant(
466         const std::string& service, const std::string& path,
467         const std::string& interface, const std::string& property)
468     {
469         return getPropertyVariant<Variant>(getBus(), service, path, interface,
470                                            property);
471     }
472 
473     /** @brief Set a property with mapper lookup. */
474     template <typename Property>
setProperty(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface,const std::string & property,Property && value)475     static void setProperty(sdbusplus::bus_t& bus, const std::string& path,
476                             const std::string& interface,
477                             const std::string& property, Property&& value)
478     {
479         using namespace std::literals::string_literals;
480 
481         std::variant<Property> varValue(std::forward<Property>(value));
482 
483         auto service = getService(bus, path, interface);
484         auto msg = callMethodAndReturn(bus, service, path,
485                                        "org.freedesktop.DBus.Properties"s,
486                                        "Set"s, interface, property, varValue);
487         if (msg.is_method_error())
488         {
489             throw DBusPropertyError{"DBus set property failed", service, path,
490                                     interface, property};
491         }
492     }
493 
494     /** @brief Set a property with mapper lookup. */
495     template <typename Property>
setProperty(const std::string & path,const std::string & interface,const std::string & property,Property && value)496     static void setProperty(const std::string& path,
497                             const std::string& interface,
498                             const std::string& property, Property&& value)
499     {
500         return setProperty(getBus(), path, interface, property,
501                            std::forward<Property>(value));
502     }
503 
504     /** @brief Set a property without mapper lookup. */
505     template <typename Property>
setProperty(sdbusplus::bus_t & bus,const std::string & service,const std::string & path,const std::string & interface,const std::string & property,Property && value)506     static void setProperty(sdbusplus::bus_t& bus, const std::string& service,
507                             const std::string& path,
508                             const std::string& interface,
509                             const std::string& property, Property&& value)
510     {
511         using namespace std::literals::string_literals;
512 
513         std::variant<Property> varValue(std::forward<Property>(value));
514 
515         auto msg = callMethodAndReturn(bus, service, path,
516                                        "org.freedesktop.DBus.Properties"s,
517                                        "Set"s, interface, property, varValue);
518         if (msg.is_method_error())
519         {
520             throw DBusPropertyError{"DBus set property failed", service, path,
521                                     interface, property};
522         }
523     }
524 
525     /** @brief Set a property without mapper lookup. */
526     template <typename Property>
setProperty(const std::string & service,const std::string & path,const std::string & interface,const std::string & property,Property && value)527     static void setProperty(const std::string& service, const std::string& path,
528                             const std::string& interface,
529                             const std::string& property, Property&& value)
530     {
531         return setProperty(getBus(), service, path, interface, property,
532                            std::forward<Property>(value));
533     }
534 
535     /** @brief Invoke method with mapper lookup. */
536     template <typename... Args>
lookupAndCallMethod(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface,const std::string & method,Args &&...args)537     static auto lookupAndCallMethod(
538         sdbusplus::bus_t& bus, const std::string& path,
539         const std::string& interface, const std::string& method, Args&&... args)
540     {
541         return callMethod(bus, getService(bus, path, interface), path,
542                           interface, method, std::forward<Args>(args)...);
543     }
544 
545     /** @brief Invoke method with mapper lookup. */
546     template <typename... Args>
lookupAndCallMethod(const std::string & path,const std::string & interface,const std::string & method,Args &&...args)547     static auto lookupAndCallMethod(const std::string& path,
548                                     const std::string& interface,
549                                     const std::string& method, Args&&... args)
550     {
551         return lookupAndCallMethod(getBus(), path, interface, method,
552                                    std::forward<Args>(args)...);
553     }
554 
555     /** @brief Invoke method and read with mapper lookup. */
556     template <typename Ret, typename... Args>
lookupCallMethodAndRead(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface,const std::string & method,Args &&...args)557     static auto lookupCallMethodAndRead(
558         sdbusplus::bus_t& bus, const std::string& path,
559         const std::string& interface, const std::string& method, Args&&... args)
560     {
561         return callMethodAndRead(bus, getService(bus, path, interface), path,
562                                  interface, method,
563                                  std::forward<Args>(args)...);
564     }
565 
566     /** @brief Invoke method and read with mapper lookup. */
567     template <typename Ret, typename... Args>
lookupCallMethodAndRead(const std::string & path,const std::string & interface,const std::string & method,Args &&...args)568     static auto lookupCallMethodAndRead(
569         const std::string& path, const std::string& interface,
570         const std::string& method, Args&&... args)
571     {
572         return lookupCallMethodAndRead<Ret>(getBus(), path, interface, method,
573                                             std::forward<Args>(args)...);
574     }
575 };
576 
577 } // namespace util
578 } // namespace fan
579 } // namespace phosphor
580