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