1 #pragma once
2 
3 #include "callback.hpp"
4 #include "data_types.hpp"
5 #include "propertywatch.hpp"
6 
7 #include <sdbusplus/bus/match.hpp>
8 #include <sdbusplus/message.hpp>
9 #include <string>
10 #include <vector>
11 
12 namespace phosphor
13 {
14 namespace dbus
15 {
16 namespace monitoring
17 {
18 
19 using MappedPropertyIndex =
20     RefKeyMap<const std::string,
21               RefKeyMap<const std::string, RefVector<const std::string>>>;
22 
23 MappedPropertyIndex convert(const PropertyIndex& index);
24 
25 template <typename DBusInterfaceType>
26 void PropertyWatch<DBusInterfaceType>::start()
27 {
28     if (alreadyRan)
29     {
30         return;
31     }
32 
33     // The index has a flat layout which is not optimal here.  Nest
34     // properties in a map of interface names in a map of object paths.
35     auto mapped = convert(index);
36 
37     for (const auto& m : mapped)
38     {
39         const auto& path = m.first.get();
40         const auto& interfaces = m.second;
41 
42         // Watch for new interfaces on this path.
43         DBusInterfaceType::addMatch(
44             sdbusplus::bus::match::rules::interfacesAdded(path),
45             [this](auto& msg)
46             // *INDENT-OFF*
47             { this->interfacesAdded(msg); });
48         // *INDENT-ON*
49 
50         // Do a query to populate the cache.  Start with a mapper query.
51         // The specific services are queried below.
52         auto getObjectFromMapper = [](const auto& path) {
53             const std::vector<std::string> queryInterfaces; // all interfaces
54             try
55             {
56                 return DBusInterfaceType::template callMethodAndRead<GetObject>(
57                     MAPPER_BUSNAME, MAPPER_PATH, MAPPER_INTERFACE, "GetObject",
58                     path, queryInterfaces);
59             }
60             catch (const sdbusplus::exception::exception&)
61             {
62                 // Paths in the configuration may not exist yet.  Prime those
63                 // later, when/if InterfacesAdded occurs.
64                 return GetObject();
65             }
66         };
67         auto mapperResp = getObjectFromMapper(path);
68 
69         for (const auto& i : interfaces)
70         {
71             const auto& interface = i.first.get();
72 
73             // Watch for property changes on this interface.
74             DBusInterfaceType::addMatch(
75                 sdbusplus::bus::match::rules::propertiesChanged(path,
76                                                                 interface),
77                 [this](auto& msg)
78                 // *INDENT-OFF*
79                 {
80                     std::string interface;
81                     msg.read(interface);
82                     auto path = msg.get_path();
83                     this->propertiesChanged(msg, path, interface);
84                 });
85             // *INDENT-ON*
86 
87             // The mapper response is a busname:[interfaces] map.  Look for
88             // each interface in the index and if found, query the service and
89             // populate the cache entries for the interface.
90             for (const auto& mr : mapperResp)
91             {
92                 const auto& busName = mr.first;
93                 const auto& mapperInterfaces = mr.second;
94                 if (mapperInterfaces.end() ==
95                     std::find(mapperInterfaces.begin(), mapperInterfaces.end(),
96                               interface))
97                 {
98                     // This interface isn't being watched.
99                     continue;
100                 }
101 
102                 // Delegate type specific property updates to subclasses.
103                 try
104                 {
105                     updateProperties(busName, path, interface);
106                 }
107                 catch (const sdbusplus::exception::exception&)
108                 {
109                     // If for some reason the path has gone away since
110                     // the mapper lookup we'll simply try again if/when
111                     // InterfacesAdded occurs the next time it shows up.
112                 }
113             }
114         }
115     }
116 
117     alreadyRan = true;
118 }
119 
120 template <typename DBusInterfaceType>
121 void PropertyWatch<DBusInterfaceType>::callback(Context ctx)
122 {
123     // Ignore callback if ignoreStartCallback is true and it's the START
124     // callback
125     if (ctx == Context::START && ignoreStartCallback)
126     {
127         return;
128     }
129     // Invoke callback if present.
130     if (this->alreadyRan && this->cb)
131     {
132         (*this->cb)(ctx);
133     }
134 }
135 
136 template <typename T, typename DBusInterfaceType>
137 void PropertyWatchOfType<T, DBusInterfaceType>::updateProperties(
138     const std::string& busName, const std::string& path,
139     const std::string& interface)
140 {
141     auto properties =
142         DBusInterfaceType::template callMethodAndRead<PropertiesChanged<T>>(
143             busName.c_str(), path.c_str(), "org.freedesktop.DBus.Properties",
144             "GetAll", interface);
145     propertiesChanged(path, interface, properties);
146 }
147 
148 template <typename T, typename DBusInterfaceType>
149 void PropertyWatchOfType<T, DBusInterfaceType>::propertiesChanged(
150     const std::string& path, const std::string& interface,
151     const PropertiesChanged<T>& properties)
152 {
153     // Update the cache for any watched properties.
154     for (const auto& p : properties)
155     {
156         auto key = std::make_tuple(path, interface, p.first);
157         auto item = this->index.find(key);
158         if (item == this->index.end())
159         {
160             // This property isn't being watched.
161             continue;
162         }
163 
164         // Run property value thru filter operations
165         auto isFiltered = false;
166         const auto& storage = std::get<storageIndex>(item->second);
167         auto value = std::get<T>(p.second);
168         if (filterOps)
169         {
170             std::any anyValue = value;
171             if ((*filterOps)(anyValue))
172             {
173                 // Property value filtered, clear it from storage so
174                 // callback functions do not use it
175                 isFiltered = true;
176                 std::get<valueIndex>(storage.get()).reset();
177             }
178         }
179         if (!isFiltered)
180         {
181             // Property value not filtered out, update
182             std::get<valueIndex>(storage.get()) = value;
183             // Invoke callback if present.
184             this->callback(Context::SIGNAL);
185         }
186     }
187 }
188 
189 template <typename T, typename DBusInterfaceType>
190 void PropertyWatchOfType<T, DBusInterfaceType>::propertiesChanged(
191     sdbusplus::message::message& msg, const std::string& path,
192     const std::string& interface)
193 {
194     PropertiesChanged<T> properties;
195     msg.read(properties);
196     propertiesChanged(path, interface, properties);
197 }
198 
199 template <typename T, typename DBusInterfaceType>
200 void PropertyWatchOfType<T, DBusInterfaceType>::interfacesAdded(
201     const std::string& path, const InterfacesAdded<T>& interfaces)
202 {
203     for (const auto& i : interfaces)
204     {
205         propertiesChanged(path, i.first, i.second);
206     }
207 }
208 
209 template <typename T, typename DBusInterfaceType>
210 void PropertyWatchOfType<T, DBusInterfaceType>::interfacesAdded(
211     sdbusplus::message::message& msg)
212 {
213     sdbusplus::message::object_path path;
214     InterfacesAdded<T> interfaces;
215     msg.read(path, interfaces);
216     interfacesAdded(path, interfaces);
217 }
218 
219 } // namespace monitoring
220 } // namespace dbus
221 } // namespace phosphor
222