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 service from the mapper. */
214     static auto getService(sdbusplus::bus::bus& bus, const std::string& path,
215                            const std::string& interface)
216     {
217         using namespace std::literals::string_literals;
218         using GetObject = std::map<std::string, std::vector<std::string>>;
219 
220         try
221         {
222             auto mapperResp = callMethodAndRead<GetObject>(
223                 bus, "xyz.openbmc_project.ObjectMapper"s,
224                 "/xyz/openbmc_project/object_mapper"s,
225                 "xyz.openbmc_project.ObjectMapper"s, "GetObject"s, path,
226                 GetObject::mapped_type{interface});
227 
228             if (mapperResp.empty())
229             {
230                 // Should never happen.  A missing object would fail
231                 // in callMethodAndRead()
232                 phosphor::logging::log<phosphor::logging::level::ERR>(
233                     "Empty mapper response on service lookup");
234                 throw DBusServiceError{path, interface};
235             }
236             return mapperResp.begin()->first;
237         }
238         catch (DBusMethodError& e)
239         {
240             throw DBusServiceError{path, interface};
241         }
242     }
243 
244     /** @brief Get service from the mapper. */
245     static auto getService(const std::string& path,
246                            const std::string& interface)
247     {
248         return getService(getBus(), path, interface);
249     }
250 
251     /** @brief Get a property with mapper lookup. */
252     template <typename Property>
253     static auto getProperty(sdbusplus::bus::bus& bus, const std::string& path,
254                             const std::string& interface,
255                             const std::string& property)
256     {
257         using namespace std::literals::string_literals;
258 
259         auto service = getService(bus, path, interface);
260         auto msg =
261             callMethod(bus, service, path, "org.freedesktop.DBus.Properties"s,
262                        "Get"s, interface, property);
263         if (msg.is_method_error())
264         {
265             throw DBusPropertyError{"DBus get property failed", service, path,
266                                     interface, property};
267         }
268         std::variant<Property> value;
269         msg.read(value);
270         return std::get<Property>(value);
271     }
272 
273     /** @brief Get a property with mapper lookup. */
274     template <typename Property>
275     static auto getProperty(const std::string& path,
276                             const std::string& interface,
277                             const std::string& property)
278     {
279         return getProperty<Property>(getBus(), path, interface, property);
280     }
281 
282     /** @brief Get a property variant with mapper lookup. */
283     template <typename Variant>
284     static auto getPropertyVariant(sdbusplus::bus::bus& bus,
285                                    const std::string& path,
286                                    const std::string& interface,
287                                    const std::string& property)
288     {
289         using namespace std::literals::string_literals;
290 
291         auto service = getService(bus, path, interface);
292         auto msg =
293             callMethod(bus, service, path, "org.freedesktop.DBus.Properties"s,
294                        "Get"s, interface, property);
295         if (msg.is_method_error())
296         {
297             throw DBusPropertyError{"DBus get property variant failed", service,
298                                     path, interface, property};
299         }
300         Variant value;
301         msg.read(value);
302         return value;
303     }
304 
305     /** @brief Get a property variant with mapper lookup. */
306     template <typename Variant>
307     static auto getPropertyVariant(const std::string& path,
308                                    const std::string& interface,
309                                    const std::string& property)
310     {
311         return getPropertyVariant<Variant>(getBus(), path, interface, property);
312     }
313 
314     /** @brief Get a property without mapper lookup. */
315     template <typename Property>
316     static auto getProperty(sdbusplus::bus::bus& bus,
317                             const std::string& service, const std::string& path,
318                             const std::string& interface,
319                             const std::string& property)
320     {
321         using namespace std::literals::string_literals;
322 
323         auto msg = callMethodAndReturn(bus, service, path,
324                                        "org.freedesktop.DBus.Properties"s,
325                                        "Get"s, interface, property);
326         if (msg.is_method_error())
327         {
328             throw DBusPropertyError{"DBus get property failed", service, path,
329                                     interface, property};
330         }
331         std::variant<Property> value;
332         msg.read(value);
333         return std::get<Property>(value);
334     }
335 
336     /** @brief Get a property without mapper lookup. */
337     template <typename Property>
338     static auto getProperty(const std::string& service, const std::string& path,
339                             const std::string& interface,
340                             const std::string& property)
341     {
342         return getProperty<Property>(getBus(), service, path, interface,
343                                      property);
344     }
345 
346     /** @brief Get a property variant without mapper lookup. */
347     template <typename Variant>
348     static auto getPropertyVariant(sdbusplus::bus::bus& bus,
349                                    const std::string& service,
350                                    const std::string& path,
351                                    const std::string& interface,
352                                    const std::string& property)
353     {
354         using namespace std::literals::string_literals;
355 
356         auto msg = callMethodAndReturn(bus, service, path,
357                                        "org.freedesktop.DBus.Properties"s,
358                                        "Get"s, interface, property);
359         if (msg.is_method_error())
360         {
361             throw DBusPropertyError{"DBus get property variant failed", service,
362                                     path, interface, property};
363         }
364         Variant value;
365         msg.read(value);
366         return value;
367     }
368 
369     /** @brief Get a property variant without mapper lookup. */
370     template <typename Variant>
371     static auto getPropertyVariant(const std::string& service,
372                                    const std::string& path,
373                                    const std::string& interface,
374                                    const std::string& property)
375     {
376         return getPropertyVariant<Variant>(getBus(), service, path, interface,
377                                            property);
378     }
379 
380     /** @brief Set a property with mapper lookup. */
381     template <typename Property>
382     static void setProperty(sdbusplus::bus::bus& bus, const std::string& path,
383                             const std::string& interface,
384                             const std::string& property, Property&& value)
385     {
386         using namespace std::literals::string_literals;
387 
388         std::variant<Property> varValue(std::forward<Property>(value));
389 
390         auto service = getService(bus, path, interface);
391         auto msg = callMethodAndReturn(bus, service, path,
392                                        "org.freedesktop.DBus.Properties"s,
393                                        "Set"s, interface, property, varValue);
394         if (msg.is_method_error())
395         {
396             throw DBusPropertyError{"DBus set property failed", service, path,
397                                     interface, property};
398         }
399     }
400 
401     /** @brief Set a property with mapper lookup. */
402     template <typename Property>
403     static void setProperty(const std::string& path,
404                             const std::string& interface,
405                             const std::string& property, Property&& value)
406     {
407         return setProperty(getBus(), path, interface, property,
408                            std::forward<Property>(value));
409     }
410 
411     /** @brief Set a property without mapper lookup. */
412     template <typename Property>
413     static void setProperty(sdbusplus::bus::bus& bus,
414                             const std::string& service, const std::string& path,
415                             const std::string& interface,
416                             const std::string& property, Property&& value)
417     {
418         using namespace std::literals::string_literals;
419 
420         std::variant<Property> varValue(std::forward<Property>(value));
421 
422         auto msg = callMethodAndReturn(bus, service, path,
423                                        "org.freedesktop.DBus.Properties"s,
424                                        "Set"s, interface, property, varValue);
425         if (msg.is_method_error())
426         {
427             throw DBusPropertyError{"DBus set property failed", service, path,
428                                     interface, property};
429         }
430     }
431 
432     /** @brief Set a property without mapper lookup. */
433     template <typename Property>
434     static void setProperty(const std::string& service, const std::string& path,
435                             const std::string& interface,
436                             const std::string& property, Property&& value)
437     {
438         return setProperty(getBus(), service, path, interface, property,
439                            std::forward<Property>(value));
440     }
441 
442     /** @brief Invoke method with mapper lookup. */
443     template <typename... Args>
444     static auto lookupAndCallMethod(sdbusplus::bus::bus& bus,
445                                     const std::string& path,
446                                     const std::string& interface,
447                                     const std::string& method, Args&&... args)
448     {
449         return callMethod(bus, getService(bus, path, interface), path,
450                           interface, method, std::forward<Args>(args)...);
451     }
452 
453     /** @brief Invoke method with mapper lookup. */
454     template <typename... Args>
455     static auto lookupAndCallMethod(const std::string& path,
456                                     const std::string& interface,
457                                     const std::string& method, Args&&... args)
458     {
459         return lookupAndCallMethod(getBus(), path, interface, method,
460                                    std::forward<Args>(args)...);
461     }
462 
463     /** @brief Invoke method and read with mapper lookup. */
464     template <typename Ret, typename... Args>
465     static auto lookupCallMethodAndRead(sdbusplus::bus::bus& bus,
466                                         const std::string& path,
467                                         const std::string& interface,
468                                         const std::string& method,
469                                         Args&&... args)
470     {
471         return callMethodAndRead(bus, getService(bus, path, interface), path,
472                                  interface, method,
473                                  std::forward<Args>(args)...);
474     }
475 
476     /** @brief Invoke method and read with mapper lookup. */
477     template <typename Ret, typename... Args>
478     static auto lookupCallMethodAndRead(const std::string& path,
479                                         const std::string& interface,
480                                         const std::string& method,
481                                         Args&&... args)
482     {
483         return lookupCallMethodAndRead<Ret>(getBus(), path, interface, method,
484                                             std::forward<Args>(args)...);
485     }
486 
487     /** @brief Invoke a method and return without checking for error. */
488     template <typename... Args>
489     static auto callMethodAndReturn(sdbusplus::bus::bus& bus,
490                                     const std::string& busName,
491                                     const std::string& path,
492                                     const std::string& interface,
493                                     const std::string& method, Args&&... args)
494     {
495         auto reqMsg = bus.new_method_call(busName.c_str(), path.c_str(),
496                                           interface.c_str(), method.c_str());
497         reqMsg.append(std::forward<Args>(args)...);
498         auto respMsg = bus.call(reqMsg);
499 
500         return respMsg;
501     }
502 };
503 
504 } // namespace util
505 } // namespace fan
506 } // namespace phosphor
507