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