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