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