1 #pragma once
2 
3 #include "dbus_types.hpp"
4 
5 #include <sdbusplus/bus/match.hpp>
6 
7 namespace openpower::pels
8 {
9 
10 namespace match_rules = sdbusplus::bus::match::rules;
11 
12 /**
13  * @class DBusWatcher
14  *
15  * The base class for the PropertyWatcher and InterfaceWatcher classes.
16  */
17 class DBusWatcher
18 {
19   public:
20     DBusWatcher() = delete;
21     virtual ~DBusWatcher() = default;
22     DBusWatcher(const DBusWatcher&) = default;
23     DBusWatcher& operator=(const DBusWatcher&) = default;
24     DBusWatcher(DBusWatcher&&) = default;
25     DBusWatcher& operator=(DBusWatcher&&) = default;
26 
27     /**
28      * @brief Constructor
29      *
30      * @param[in] path - The D-Bus path that will be watched
31      * @param[in] interface - The D-Bus interface that will be watched
32      */
DBusWatcher(const std::string & path,const std::string & interface)33     DBusWatcher(const std::string& path, const std::string& interface) :
34         _path(path), _interface(interface)
35     {}
36 
37   protected:
38     /**
39      * @brief The D-Bus path
40      */
41     std::string _path;
42 
43     /**
44      * @brief The D-Bus interface
45      */
46     std::string _interface;
47 
48     /**
49      * @brief The match objects for the propertiesChanged and
50      *        interfacesAdded signals.
51      */
52     std::vector<sdbusplus::bus::match_t> _matches;
53 };
54 
55 /**
56  * @class PropertyWatcher
57  *
58  * This class allows the user to be kept up to data with a D-Bus
59  * property's value.  It does this by calling a user specified function
60  * that is passed the variant that contains the property's value when:
61  *
62  * 1) The property is read when the class is constructed, if
63  *    the property is on D-Bus at the time.
64  * 2) The property changes (via a property changed signal).
65  * 3) An interfacesAdded signal is received with that property.
66  *
67  * The DataInterface class is used to access D-Bus, and is a template
68  * to avoid any circular include issues as that class is one of the
69  * users of this one.
70  */
71 template <typename DataIface>
72 class PropertyWatcher : public DBusWatcher
73 {
74   public:
75     PropertyWatcher() = delete;
76     ~PropertyWatcher() = default;
77     PropertyWatcher(const PropertyWatcher&) = delete;
78     PropertyWatcher& operator=(const PropertyWatcher&) = delete;
79     PropertyWatcher(PropertyWatcher&&) = delete;
80     PropertyWatcher& operator=(PropertyWatcher&&) = delete;
81 
82     using PropertySetFunc = std::function<void(const DBusValue&)>;
83 
84     /**
85      * @brief Constructor
86      *
87      * Reads the property if it is on D-Bus, and sets up the match
88      * objects for the propertiesChanged and interfacesAdded signals.
89      *
90      * @param[in] bus - The sdbusplus bus object
91      * @param[in] path - The D-Bus path of the property
92      * @param[in] interface - The D-Bus interface that contains the property
93      * @param[in] propertyName - The property name
94      * @param[in] service - The D-Bus service to use for the property read.
95      *                      Can be empty to look it up instead.
96      * @param[in] dataIface - The DataInterface object
97      * @param[in] func - The callback used any time the property is read
98      */
PropertyWatcher(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface,const std::string & propertyName,const std::string & service,const DataIface & dataIface,PropertySetFunc func)99     PropertyWatcher(sdbusplus::bus_t& bus, const std::string& path,
100                     const std::string& interface,
101                     const std::string& propertyName, const std::string& service,
102                     const DataIface& dataIface, PropertySetFunc func) :
103         DBusWatcher(path, interface), _name(propertyName), _setFunc(func)
104     {
105         _matches.emplace_back(
106             bus, match_rules::propertiesChanged(_path, _interface),
107             std::bind(std::mem_fn(&PropertyWatcher::propChanged), this,
108                       std::placeholders::_1));
109 
110         _matches.emplace_back(
111             bus,
112             match_rules::interfacesAdded() + match_rules::argNpath(0, _path),
113             std::bind(std::mem_fn(&PropertyWatcher::interfaceAdded), this,
114                       std::placeholders::_1));
115 
116         try
117         {
118             read(dataIface, service);
119         }
120         catch (const sdbusplus::exception_t& e)
121         {
122             // Path doesn't exist now
123         }
124     }
125 
126     /**
127      * @brief Constructor
128      *
129      * Reads the property if it is on D-Bus, and sets up the match
130      * objects for the propertiesChanged and interfacesAdded signals.
131      *
132      * Unlike the other constructor, this contructor doesn't take the
133      * service to use for the property read so it will look it up with
134      * an ObjectMapper GetObject call.
135      *
136      * @param[in] bus - The sdbusplus bus object
137      * @param[in] path - The D-Bus path of the property
138      * @param[in] interface - The D-Bus interface that contains the property
139      * @param[in] propertyName - The property name
140      * @param[in] dataIface - The DataInterface object
141      * @param[in] func - The callback used any time the property is read
142      */
PropertyWatcher(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface,const std::string & propertyName,const DataIface & dataIface,PropertySetFunc func)143     PropertyWatcher(sdbusplus::bus_t& bus, const std::string& path,
144                     const std::string& interface,
145                     const std::string& propertyName, const DataIface& dataIface,
146                     PropertySetFunc func) :
147         PropertyWatcher(bus, path, interface, propertyName, "", dataIface, func)
148     {}
149 
150     /**
151      * @brief Reads the property on D-Bus, and calls
152      *        the user defined function with the value.
153      *
154      * If the passed in service is empty, look up the service to use.
155      *
156      * @param[in] dataIface - The DataInterface object
157      * @param[in] service - The D-Bus service to make the getProperty
158      *                      call with, if not empty
159      */
read(const DataIface & dataIface,std::string service)160     void read(const DataIface& dataIface, std::string service)
161     {
162         if (service.empty())
163         {
164             service = dataIface.getService(_path, _interface);
165         }
166 
167         if (!service.empty())
168         {
169             DBusValue value;
170             dataIface.getProperty(service, _path, _interface, _name, value);
171 
172             _setFunc(value);
173         }
174     }
175 
176     /**
177      * @brief The propertiesChanged callback
178      *
179      * Calls the user defined function with the property value
180      *
181      * @param[in] msg - The sdbusplus message object
182      */
propChanged(sdbusplus::message_t & msg)183     void propChanged(sdbusplus::message_t& msg)
184     {
185         DBusInterface interface;
186         DBusPropertyMap properties;
187 
188         msg.read(interface, properties);
189 
190         auto prop = properties.find(_name);
191         if (prop != properties.end())
192         {
193             _setFunc(prop->second);
194         }
195     }
196 
197     /**
198      * @brief The interfacesAdded callback
199      *
200      * Calls the user defined function with the property value
201      *
202      * @param[in] msg - The sdbusplus message object
203      */
interfaceAdded(sdbusplus::message_t & msg)204     void interfaceAdded(sdbusplus::message_t& msg)
205     {
206         sdbusplus::message::object_path path;
207         DBusInterfaceMap interfaces;
208 
209         msg.read(path, interfaces);
210 
211         auto iface = interfaces.find(_interface);
212         if (iface != interfaces.end())
213         {
214             auto prop = iface->second.find(_name);
215             if (prop != iface->second.end())
216             {
217                 _setFunc(prop->second);
218             }
219         }
220     }
221 
222   private:
223     /**
224      * @brief The D-Bus property name
225      */
226     std::string _name;
227 
228     /**
229      * @brief The function that will be called any time the
230      *        property is read.
231      */
232     PropertySetFunc _setFunc;
233 };
234 
235 /**
236  * @class InterfaceWatcher
237  *
238  * This class allows the user to be kept up to data with a D-Bus
239  * interface's properties..  It does this by calling a user specified
240  * function that is passed a map of the D-Bus property names and values
241  * on that interface when:
242  *
243  * 1) The interface is read when the class is constructed, if
244  *    the interface is on D-Bus at the time.
245  * 2) The interface has a property that changes (via a property changed signal).
246  * 3) An interfacesAdded signal is received.
247  *
248  * The DataInterface class is used to access D-Bus, and is a template
249  * to avoid any circular include issues as that class is one of the
250  * users of this one.
251  */
252 template <typename DataIface>
253 class InterfaceWatcher : public DBusWatcher
254 {
255   public:
256     InterfaceWatcher() = delete;
257     ~InterfaceWatcher() = default;
258     InterfaceWatcher(const InterfaceWatcher&) = delete;
259     InterfaceWatcher& operator=(const InterfaceWatcher&) = delete;
260     InterfaceWatcher(InterfaceWatcher&&) = delete;
261     InterfaceWatcher& operator=(InterfaceWatcher&&) = delete;
262 
263     using InterfaceSetFunc = std::function<void(const DBusPropertyMap&)>;
264 
265     /**
266      * @brief Constructor
267      *
268      * Reads all properties on the interface if it is on D-Bus,
269      * and sets up the match objects for the propertiesChanged
270      * and interfacesAdded signals.
271      *
272      * @param[in] bus - The sdbusplus bus object
273      * @param[in] path - The D-Bus path of the property
274      * @param[in] interface - The D-Bus interface that contains the property
275      * @param[in] dataIface - The DataInterface object
276      * @param[in] func - The callback used any time the property is read
277      */
InterfaceWatcher(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface,const DataIface & dataIface,InterfaceSetFunc func)278     InterfaceWatcher(sdbusplus::bus_t& bus, const std::string& path,
279                      const std::string& interface, const DataIface& dataIface,
280                      InterfaceSetFunc func) :
281         DBusWatcher(path, interface), _setFunc(func)
282     {
283         _matches.emplace_back(
284             bus, match_rules::propertiesChanged(_path, _interface),
285             std::bind(std::mem_fn(&InterfaceWatcher::propChanged), this,
286                       std::placeholders::_1));
287 
288         _matches.emplace_back(
289             bus,
290             match_rules::interfacesAdded() + match_rules::argNpath(0, _path),
291             std::bind(std::mem_fn(&InterfaceWatcher::interfaceAdded), this,
292                       std::placeholders::_1));
293 
294         try
295         {
296             read(dataIface);
297         }
298         catch (const sdbusplus::exception_t& e)
299         {
300             // Path doesn't exist now
301         }
302     }
303 
304     /**
305      * @brief Reads the interface's properties on D-Bus, and
306      * calls the the user defined function with the property map.
307      *
308      * @param[in] dataIface - The DataInterface object
309      */
read(const DataIface & dataIface)310     void read(const DataIface& dataIface)
311     {
312         auto service = dataIface.getService(_path, _interface);
313         if (!service.empty())
314         {
315             auto properties =
316                 dataIface.getAllProperties(service, _path, _interface);
317 
318             _setFunc(properties);
319         }
320     }
321 
322     /**
323      * @brief The propertiesChanged callback
324      *
325      * Calls the user defined function with the property map.  Only the
326      * properties that changed will be in the map.
327      *
328      * @param[in] msg - The sdbusplus message object
329      */
propChanged(sdbusplus::message_t & msg)330     void propChanged(sdbusplus::message_t& msg)
331     {
332         DBusInterface interface;
333         DBusPropertyMap properties;
334 
335         msg.read(interface, properties);
336 
337         _setFunc(properties);
338     }
339 
340     /**
341      * @brief The interfacesAdded callback
342      *
343      * Calls the user defined function with the property map
344      *
345      * @param[in] msg - The sdbusplus message object
346      */
interfaceAdded(sdbusplus::message_t & msg)347     void interfaceAdded(sdbusplus::message_t& msg)
348     {
349         sdbusplus::message::object_path path;
350         DBusInterfaceMap interfaces;
351 
352         msg.read(path, interfaces);
353 
354         auto iface = interfaces.find(_interface);
355         if (iface != interfaces.end())
356         {
357             _setFunc(iface->second);
358         }
359     }
360 
361   private:
362     /**
363      * @brief The function that will be called any time the
364      *        interface is read.
365      */
366     InterfaceSetFunc _setFunc;
367 };
368 
369 } // namespace openpower::pels
370