1 #pragma once
2 
3 #include <phosphor-logging/elog-errors.hpp>
4 #include <phosphor-logging/elog.hpp>
5 #include <phosphor-logging/log.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>
156     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)157         callMethodAndRead(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(const std::string& busName,
171                                   const std::string& path,
172                                   const std::string& interface,
173                                   const std::string& method, Args&&... args)
174     {
175         return callMethodAndRead<Ret>(getBus(), busName, path, interface,
176                                       method, std::forward<Args>(args)...);
177     }
178 
179     /** @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)180     static auto getSubTreeRaw(sdbusplus::bus_t& bus, const std::string& path,
181                               const std::string& interface, int32_t depth)
182     {
183         using namespace std::literals::string_literals;
184 
185         using Path = std::string;
186         using Intf = std::string;
187         using Serv = std::string;
188         using Intfs = std::vector<Intf>;
189         using Objects = std::map<Path, std::map<Serv, Intfs>>;
190         Intfs intfs = {interface};
191 
192         return callMethodAndRead<Objects>(bus,
193                                           "xyz.openbmc_project.ObjectMapper"s,
194                                           "/xyz/openbmc_project/object_mapper"s,
195                                           "xyz.openbmc_project.ObjectMapper"s,
196                                           "GetSubTree"s, path, depth, intfs);
197     }
198 
199     /** @brief Get subtree from the mapper without checking response,
200      * (multiple interfaces version). */
getSubTreeRaw(sdbusplus::bus_t & bus,const std::string & path,const std::vector<std::string> & intfs,int32_t depth)201     static auto getSubTreeRaw(sdbusplus::bus_t& bus, const std::string& path,
202                               const std::vector<std::string>& intfs,
203                               int32_t depth)
204     {
205         using namespace std::literals::string_literals;
206 
207         using Path = std::string;
208         using Intf = std::string;
209         using Serv = std::string;
210         using Intfs = std::vector<Intf>;
211         using Objects = std::map<Path, std::map<Serv, Intfs>>;
212 
213         return callMethodAndRead<Objects>(bus,
214                                           "xyz.openbmc_project.ObjectMapper"s,
215                                           "/xyz/openbmc_project/object_mapper"s,
216                                           "xyz.openbmc_project.ObjectMapper"s,
217                                           "GetSubTree"s, path, depth, intfs);
218     }
219 
220     /** @brief Get subtree from the mapper. */
getSubTree(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface,int32_t depth)221     static auto getSubTree(sdbusplus::bus_t& bus, const std::string& path,
222                            const std::string& interface, int32_t depth)
223     {
224         auto mapperResp = getSubTreeRaw(bus, path, interface, depth);
225         if (mapperResp.empty())
226         {
227             phosphor::logging::log<phosphor::logging::level::ERR>(
228                 "Empty response from mapper GetSubTree",
229                 phosphor::logging::entry("SUBTREE=%s", path.c_str()),
230                 phosphor::logging::entry("INTERFACE=%s", interface.c_str()),
231                 phosphor::logging::entry("DEPTH=%u", depth));
232             phosphor::logging::elog<detail::errors::InternalFailure>();
233         }
234         return mapperResp;
235     }
236 
237     /** @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)238     static auto getSubTreePathsRaw(sdbusplus::bus_t& bus,
239                                    const std::string& path,
240                                    const std::string& interface, int32_t depth)
241     {
242         using namespace std::literals::string_literals;
243 
244         using Path = std::string;
245         using Intf = std::string;
246         using Intfs = std::vector<Intf>;
247         using ObjectPaths = std::vector<Path>;
248         Intfs intfs = {interface};
249 
250         return callMethodAndRead<ObjectPaths>(
251             bus, "xyz.openbmc_project.ObjectMapper"s,
252             "/xyz/openbmc_project/object_mapper"s,
253             "xyz.openbmc_project.ObjectMapper"s, "GetSubTreePaths"s, path,
254             depth, intfs);
255     }
256 
257     /** @brief Get subtree paths from the mapper. */
getSubTreePaths(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface,int32_t depth)258     static auto getSubTreePaths(sdbusplus::bus_t& bus, const std::string& path,
259                                 const std::string& interface, int32_t depth)
260     {
261         auto mapperResp = getSubTreePathsRaw(bus, path, interface, depth);
262         if (mapperResp.empty())
263         {
264             phosphor::logging::log<phosphor::logging::level::ERR>(
265                 "Empty response from mapper GetSubTreePaths",
266                 phosphor::logging::entry("SUBTREE=%s", path.c_str()),
267                 phosphor::logging::entry("INTERFACE=%s", interface.c_str()),
268                 phosphor::logging::entry("DEPTH=%u", depth));
269             phosphor::logging::elog<detail::errors::InternalFailure>();
270         }
271         return mapperResp;
272     }
273 
274     /** @brief Get service from the mapper without checking response. */
getServiceRaw(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface)275     static auto getServiceRaw(sdbusplus::bus_t& bus, const std::string& path,
276                               const std::string& interface)
277     {
278         using namespace std::literals::string_literals;
279         using GetObject = std::map<std::string, std::vector<std::string>>;
280 
281         return callMethodAndRead<GetObject>(
282             bus, "xyz.openbmc_project.ObjectMapper"s,
283             "/xyz/openbmc_project/object_mapper"s,
284             "xyz.openbmc_project.ObjectMapper"s, "GetObject"s, path,
285             GetObject::mapped_type{interface});
286     }
287 
288     /** @brief Get service from the mapper. */
getService(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface)289     static auto getService(sdbusplus::bus_t& bus, const std::string& path,
290                            const std::string& interface)
291     {
292         try
293         {
294             auto mapperResp = getServiceRaw(bus, path, interface);
295 
296             if (mapperResp.empty())
297             {
298                 // Should never happen.  A missing object would fail
299                 // in callMethodAndRead()
300                 phosphor::logging::log<phosphor::logging::level::ERR>(
301                     "Empty mapper response on service lookup");
302                 throw DBusServiceError{path, interface};
303             }
304             return mapperResp.begin()->first;
305         }
306         catch (const DBusMethodError& e)
307         {
308             throw DBusServiceError{path, interface};
309         }
310     }
311 
312     /** @brief Get service from the mapper. */
getService(const std::string & path,const std::string & interface)313     static auto getService(const std::string& path,
314                            const std::string& interface)
315     {
316         return getService(getBus(), path, interface);
317     }
318 
319     /** @brief Get managed objects. */
320     template <typename Variant>
getManagedObjects(sdbusplus::bus_t & bus,const std::string & service,const std::string & path)321     static auto getManagedObjects(sdbusplus::bus_t& bus,
322                                   const std::string& service,
323                                   const std::string& path)
324     {
325         using namespace std::literals::string_literals;
326 
327         using Path = sdbusplus::message::object_path;
328         using Intf = std::string;
329         using Prop = std::string;
330         using GetManagedObjects =
331             std::map<Path, std::map<Intf, std::map<Prop, Variant>>>;
332 
333         return callMethodAndRead<GetManagedObjects>(
334             bus, service, path, "org.freedesktop.DBus.ObjectManager"s,
335             "GetManagedObjects"s);
336     }
337 
338     /** @brief Get a property with mapper lookup. */
339     template <typename Property>
getProperty(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface,const std::string & property)340     static auto getProperty(sdbusplus::bus_t& bus, const std::string& path,
341                             const std::string& interface,
342                             const std::string& property)
343     {
344         using namespace std::literals::string_literals;
345 
346         auto service = getService(bus, path, interface);
347         auto msg = callMethod(bus, service, path,
348                               "org.freedesktop.DBus.Properties"s, "Get"s,
349                               interface, property);
350         if (msg.is_method_error())
351         {
352             throw DBusPropertyError{"DBus get property failed", service, path,
353                                     interface, property};
354         }
355         std::variant<Property> value;
356         msg.read(value);
357         return std::get<Property>(value);
358     }
359 
360     /** @brief Get a property with mapper lookup. */
361     template <typename Property>
getProperty(const std::string & path,const std::string & interface,const std::string & property)362     static auto getProperty(const std::string& path,
363                             const std::string& interface,
364                             const std::string& property)
365     {
366         return getProperty<Property>(getBus(), path, interface, property);
367     }
368 
369     /** @brief Get a property variant with mapper lookup. */
370     template <typename Variant>
getPropertyVariant(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface,const std::string & property)371     static auto getPropertyVariant(sdbusplus::bus_t& bus,
372                                    const std::string& path,
373                                    const std::string& interface,
374                                    const std::string& property)
375     {
376         using namespace std::literals::string_literals;
377 
378         auto service = getService(bus, path, interface);
379         auto msg = callMethod(bus, service, path,
380                               "org.freedesktop.DBus.Properties"s, "Get"s,
381                               interface, property);
382         if (msg.is_method_error())
383         {
384             throw DBusPropertyError{"DBus get property variant failed", service,
385                                     path, interface, property};
386         }
387         Variant value;
388         msg.read(value);
389         return value;
390     }
391 
392     /** @brief Get a property variant with mapper lookup. */
393     template <typename Variant>
getPropertyVariant(const std::string & path,const std::string & interface,const std::string & property)394     static auto getPropertyVariant(const std::string& path,
395                                    const std::string& interface,
396                                    const std::string& property)
397     {
398         return getPropertyVariant<Variant>(getBus(), path, interface, property);
399     }
400 
401     /** @brief Invoke a method and return without checking for error. */
402     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)403     static auto callMethodAndReturn(sdbusplus::bus_t& bus,
404                                     const std::string& busName,
405                                     const std::string& path,
406                                     const std::string& interface,
407                                     const std::string& method, Args&&... args)
408     {
409         auto reqMsg = bus.new_method_call(busName.c_str(), path.c_str(),
410                                           interface.c_str(), method.c_str());
411         reqMsg.append(std::forward<Args>(args)...);
412         auto respMsg = bus.call(reqMsg);
413 
414         return respMsg;
415     }
416 
417     /** @brief Get a property without mapper lookup. */
418     template <typename Property>
getProperty(sdbusplus::bus_t & bus,const std::string & service,const std::string & path,const std::string & interface,const std::string & property)419     static auto getProperty(sdbusplus::bus_t& bus, const std::string& service,
420                             const std::string& path,
421                             const std::string& interface,
422                             const std::string& property)
423     {
424         using namespace std::literals::string_literals;
425 
426         auto msg = callMethodAndReturn(bus, service, path,
427                                        "org.freedesktop.DBus.Properties"s,
428                                        "Get"s, interface, property);
429         if (msg.is_method_error())
430         {
431             throw DBusPropertyError{"DBus get property failed", service, path,
432                                     interface, property};
433         }
434         std::variant<Property> value;
435         msg.read(value);
436         return std::get<Property>(value);
437     }
438 
439     /** @brief Get a property without mapper lookup. */
440     template <typename Property>
getProperty(const std::string & service,const std::string & path,const std::string & interface,const std::string & property)441     static auto getProperty(const std::string& service, const std::string& path,
442                             const std::string& interface,
443                             const std::string& property)
444     {
445         return getProperty<Property>(getBus(), service, path, interface,
446                                      property);
447     }
448 
449     /** @brief Get a property variant without mapper lookup. */
450     template <typename Variant>
getPropertyVariant(sdbusplus::bus_t & bus,const std::string & service,const std::string & path,const std::string & interface,const std::string & property)451     static auto getPropertyVariant(sdbusplus::bus_t& bus,
452                                    const std::string& service,
453                                    const std::string& path,
454                                    const std::string& interface,
455                                    const std::string& property)
456     {
457         using namespace std::literals::string_literals;
458 
459         auto msg = callMethodAndReturn(bus, service, path,
460                                        "org.freedesktop.DBus.Properties"s,
461                                        "Get"s, interface, property);
462         if (msg.is_method_error())
463         {
464             throw DBusPropertyError{"DBus get property variant failed", service,
465                                     path, interface, property};
466         }
467         Variant value;
468         msg.read(value);
469         return value;
470     }
471 
472     /** @brief Get a property variant without mapper lookup. */
473     template <typename Variant>
getPropertyVariant(const std::string & service,const std::string & path,const std::string & interface,const std::string & property)474     static auto getPropertyVariant(const std::string& service,
475                                    const std::string& path,
476                                    const std::string& interface,
477                                    const std::string& property)
478     {
479         return getPropertyVariant<Variant>(getBus(), service, path, interface,
480                                            property);
481     }
482 
483     /** @brief Set a property with mapper lookup. */
484     template <typename Property>
setProperty(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface,const std::string & property,Property && value)485     static void setProperty(sdbusplus::bus_t& bus, const std::string& path,
486                             const std::string& interface,
487                             const std::string& property, Property&& value)
488     {
489         using namespace std::literals::string_literals;
490 
491         std::variant<Property> varValue(std::forward<Property>(value));
492 
493         auto service = getService(bus, path, interface);
494         auto msg = callMethodAndReturn(bus, service, path,
495                                        "org.freedesktop.DBus.Properties"s,
496                                        "Set"s, interface, property, varValue);
497         if (msg.is_method_error())
498         {
499             throw DBusPropertyError{"DBus set property failed", service, path,
500                                     interface, property};
501         }
502     }
503 
504     /** @brief Set a property with mapper lookup. */
505     template <typename Property>
setProperty(const std::string & path,const std::string & interface,const std::string & property,Property && value)506     static void setProperty(const std::string& path,
507                             const std::string& interface,
508                             const std::string& property, Property&& value)
509     {
510         return setProperty(getBus(), path, interface, property,
511                            std::forward<Property>(value));
512     }
513 
514     /** @brief Set a property without mapper lookup. */
515     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)516     static void setProperty(sdbusplus::bus_t& bus, const std::string& service,
517                             const std::string& path,
518                             const std::string& interface,
519                             const std::string& property, Property&& value)
520     {
521         using namespace std::literals::string_literals;
522 
523         std::variant<Property> varValue(std::forward<Property>(value));
524 
525         auto msg = callMethodAndReturn(bus, service, path,
526                                        "org.freedesktop.DBus.Properties"s,
527                                        "Set"s, interface, property, varValue);
528         if (msg.is_method_error())
529         {
530             throw DBusPropertyError{"DBus set property failed", service, path,
531                                     interface, property};
532         }
533     }
534 
535     /** @brief Set a property without mapper lookup. */
536     template <typename Property>
setProperty(const std::string & service,const std::string & path,const std::string & interface,const std::string & property,Property && value)537     static void setProperty(const std::string& service, const std::string& path,
538                             const std::string& interface,
539                             const std::string& property, Property&& value)
540     {
541         return setProperty(getBus(), service, path, interface, property,
542                            std::forward<Property>(value));
543     }
544 
545     /** @brief Invoke method with mapper lookup. */
546     template <typename... Args>
lookupAndCallMethod(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface,const std::string & method,Args &&...args)547     static auto lookupAndCallMethod(sdbusplus::bus_t& bus,
548                                     const std::string& path,
549                                     const std::string& interface,
550                                     const std::string& method, Args&&... args)
551     {
552         return callMethod(bus, getService(bus, path, interface), path,
553                           interface, method, std::forward<Args>(args)...);
554     }
555 
556     /** @brief Invoke method with mapper lookup. */
557     template <typename... Args>
lookupAndCallMethod(const std::string & path,const std::string & interface,const std::string & method,Args &&...args)558     static auto lookupAndCallMethod(const std::string& path,
559                                     const std::string& interface,
560                                     const std::string& method, Args&&... args)
561     {
562         return lookupAndCallMethod(getBus(), path, interface, method,
563                                    std::forward<Args>(args)...);
564     }
565 
566     /** @brief Invoke method and read with mapper lookup. */
567     template <typename Ret, typename... Args>
568     static auto
lookupCallMethodAndRead(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface,const std::string & method,Args &&...args)569         lookupCallMethodAndRead(sdbusplus::bus_t& bus, const std::string& path,
570                                 const std::string& interface,
571                                 const std::string& method, Args&&... args)
572     {
573         return callMethodAndRead(bus, getService(bus, path, interface), path,
574                                  interface, method,
575                                  std::forward<Args>(args)...);
576     }
577 
578     /** @brief Invoke method and read with mapper lookup. */
579     template <typename Ret, typename... Args>
lookupCallMethodAndRead(const std::string & path,const std::string & interface,const std::string & method,Args &&...args)580     static auto lookupCallMethodAndRead(const std::string& path,
581                                         const std::string& interface,
582                                         const std::string& method,
583                                         Args&&... args)
584     {
585         return lookupCallMethodAndRead<Ret>(getBus(), path, interface, method,
586                                             std::forward<Args>(args)...);
587     }
588 };
589 
590 } // namespace util
591 } // namespace fan
592 } // namespace phosphor
593