xref: /openbmc/openpower-vpd-parser/vpd-manager/src/listener.cpp (revision 3e1cb49d2d469b79cafbd6372d071acb66ee612e)
1 #include "listener.hpp"
2 
3 #include "constants.hpp"
4 #include "event_logger.hpp"
5 #include "exceptions.hpp"
6 #include "logger.hpp"
7 #include "utility/common_utility.hpp"
8 #include "utility/dbus_utility.hpp"
9 #include "utility/json_utility.hpp"
10 
11 namespace vpd
12 {
13 Listener::Listener(
14     const std::shared_ptr<Worker>& i_worker,
15     const std::shared_ptr<sdbusplus::asio::connection>& i_asioConnection) :
16     m_worker(i_worker), m_asioConnection(i_asioConnection)
17 {}
18 
19 void Listener::registerHostStateChangeCallback() const noexcept
20 {
21     try
22     {
23         static std::shared_ptr<sdbusplus::bus::match_t> l_hostState =
24             std::make_shared<sdbusplus::bus::match_t>(
25                 *m_asioConnection,
26                 sdbusplus::bus::match::rules::propertiesChanged(
27                     constants::hostObjectPath, constants::hostInterface),
28                 [this](sdbusplus::message_t& i_msg) {
29                     hostStateChangeCallBack(i_msg);
30                 });
31     }
32     catch (const std::exception& l_ex)
33     {
34         EventLogger::createSyncPel(
35             EventLogger::getErrorType(l_ex), types::SeverityType::Informational,
36             __FILE__, __FUNCTION__, 0,
37             "Register Host state change callback failed, reason: " +
38                 std::string(l_ex.what()),
39             std::nullopt, std::nullopt, std::nullopt, std::nullopt);
40     }
41 }
42 
43 void Listener::hostStateChangeCallBack(
44     sdbusplus::message_t& i_msg) const noexcept
45 {
46     try
47     {
48         if (i_msg.is_method_error())
49         {
50             throw std::runtime_error(
51                 "Error reading callback message for host state");
52         }
53 
54         std::string l_objectPath;
55         types::PropertyMap l_propMap;
56         i_msg.read(l_objectPath, l_propMap);
57 
58         const auto l_itr = l_propMap.find("CurrentHostState");
59 
60         if (l_itr == l_propMap.end())
61         {
62             // CurrentHostState is not found in the callback message
63             return;
64         }
65 
66         if (auto l_hostState = std::get_if<std::string>(&(l_itr->second)))
67         {
68             // implies system is moving from standby to power on state
69             if (*l_hostState == "xyz.openbmc_project.State.Host.HostState."
70                                 "TransitioningToRunning")
71             {
72                 // TODO: check for all the essential FRUs in the system.
73 
74                 if (m_worker.get() != nullptr)
75                 {
76                     // Perform recollection.
77                     m_worker->performVpdRecollection();
78                 }
79                 else
80                 {
81                     logging::logMessage(
82                         "Failed to get worker object, Abort re-collection");
83                 }
84             }
85         }
86         else
87         {
88             throw std::runtime_error(
89                 "Invalid type recieved in variant for host state.");
90         }
91     }
92     catch (const std::exception& l_ex)
93     {
94         EventLogger::createSyncPel(
95             EventLogger::getErrorType(l_ex), types::SeverityType::Informational,
96             __FILE__, __FUNCTION__, 0,
97             "Host state change callback failed, reason: " +
98                 std::string(l_ex.what()),
99             std::nullopt, std::nullopt, std::nullopt, std::nullopt);
100     }
101 }
102 
103 void Listener::registerAssetTagChangeCallback() const noexcept
104 {
105     try
106     {
107         static std::shared_ptr<sdbusplus::bus::match_t> l_assetMatch =
108             std::make_shared<sdbusplus::bus::match_t>(
109                 *m_asioConnection,
110                 sdbusplus::bus::match::rules::propertiesChanged(
111                     constants::systemInvPath, constants::assetTagInf),
112                 [this](sdbusplus::message_t& l_msg) {
113                     assetTagChangeCallback(l_msg);
114                 });
115     }
116     catch (const std::exception& l_ex)
117     {
118         EventLogger::createSyncPel(
119             EventLogger::getErrorType(l_ex), types::SeverityType::Informational,
120             __FILE__, __FUNCTION__, 0,
121             "Register AssetTag change callback failed, reason: " +
122                 std::string(l_ex.what()),
123             std::nullopt, std::nullopt, std::nullopt, std::nullopt);
124     }
125 }
126 
127 void Listener::assetTagChangeCallback(
128     sdbusplus::message_t& i_msg) const noexcept
129 {
130     try
131     {
132         if (i_msg.is_method_error())
133         {
134             throw std::runtime_error(
135                 "Error reading callback msg for asset tag.");
136         }
137 
138         std::string l_objectPath;
139         types::PropertyMap l_propMap;
140         i_msg.read(l_objectPath, l_propMap);
141 
142         const auto& l_itrToAssetTag = l_propMap.find("AssetTag");
143         if (l_itrToAssetTag != l_propMap.end())
144         {
145             if (auto l_assetTag =
146                     std::get_if<std::string>(&(l_itrToAssetTag->second)))
147             {
148                 // Call Notify to persist the AssetTag
149                 types::ObjectMap l_objectMap = {
150                     {sdbusplus::message::object_path(constants::systemInvPath),
151                      {{constants::assetTagInf, {{"AssetTag", *l_assetTag}}}}}};
152 
153                 // Notify PIM
154                 if (!dbusUtility::callPIM(move(l_objectMap)))
155                 {
156                     throw std::runtime_error(
157                         "Call to PIM failed for asset tag update.");
158                 }
159             }
160         }
161         else
162         {
163             throw std::runtime_error(
164                 "Could not find asset tag in callback message.");
165         }
166     }
167     catch (const std::exception& l_ex)
168     {
169         EventLogger::createSyncPel(
170             EventLogger::getErrorType(l_ex), types::SeverityType::Informational,
171             __FILE__, __FUNCTION__, 0,
172             "AssetTag update failed, reason: " + std::string(l_ex.what()),
173             std::nullopt, std::nullopt, std::nullopt, std::nullopt);
174     }
175 }
176 
177 void Listener::registerPresenceChangeCallback() noexcept
178 {
179     try
180     {
181         // get list of FRUs for which presence monitoring is required
182         const auto& l_listOfFrus = jsonUtility::getFrusWithPresenceMonitoring(
183             m_worker->getSysCfgJsonObj());
184 
185         for (const auto& l_inventoryPath : l_listOfFrus)
186         {
187             std::shared_ptr<sdbusplus::bus::match_t> l_fruPresenceMatch =
188                 std::make_shared<sdbusplus::bus::match_t>(
189                     *m_asioConnection,
190                     sdbusplus::bus::match::rules::propertiesChanged(
191                         l_inventoryPath, constants::inventoryItemInf),
192                     [this](sdbusplus::message_t& i_msg) {
193                         presentPropertyChangeCallback(i_msg);
194                     });
195 
196             // save the match object to map
197             m_fruPresenceMatchObjectMap[l_inventoryPath] = l_fruPresenceMatch;
198         }
199     }
200     catch (const std::exception& l_ex)
201     {
202         EventLogger::createSyncPel(
203             EventLogger::getErrorType(l_ex), types::SeverityType::Informational,
204             __FILE__, __FUNCTION__, 0,
205             "Register presence change callback failed, reason: " +
206                 std::string(l_ex.what()),
207             std::nullopt, std::nullopt, std::nullopt, std::nullopt);
208     }
209 }
210 
211 void Listener::presentPropertyChangeCallback(
212     sdbusplus::message_t& i_msg) const noexcept
213 {
214     try
215     {
216         if (i_msg.is_method_error())
217         {
218             throw DbusException(
219                 "Error reading callback message for Present property change");
220         }
221 
222         std::string l_interface;
223         types::PropertyMap l_propMap;
224         i_msg.read(l_interface, l_propMap);
225 
226         const std::string l_objectPath{i_msg.get_path()};
227 
228         const auto l_itr = l_propMap.find("Present");
229         if (l_itr == l_propMap.end())
230         {
231             // Present is not found in the callback message
232             return;
233         }
234 
235         if (auto l_present = std::get_if<bool>(&(l_itr->second)))
236         {
237             *l_present ? m_worker->collectSingleFruVpd(l_objectPath)
238                        : m_worker->deleteFruVpd(l_objectPath);
239         }
240         else
241         {
242             throw DbusException(
243                 "Invalid type recieved in variant for present property");
244         }
245     }
246     catch (const std::exception& l_ex)
247     {
248         EventLogger::createSyncPel(
249             EventLogger::getErrorType(l_ex), types::SeverityType::Informational,
250             __FILE__, __FUNCTION__, 0,
251             "Process presence change callback failed, reason: " +
252                 std::string(l_ex.what()),
253             std::nullopt, std::nullopt, std::nullopt, std::nullopt);
254     }
255 }
256 
257 void Listener::registerCorrPropCallBack(
258     const std::string& i_correlatedPropJsonFile) noexcept
259 {
260     try
261     {
262         m_correlatedPropJson =
263             jsonUtility::getParsedJson(i_correlatedPropJsonFile);
264         if (m_correlatedPropJson.empty())
265         {
266             throw JsonException("Failed to parse correlated properties JSON",
267                                 i_correlatedPropJsonFile);
268         }
269 
270         const nlohmann::json& l_serviceJsonObjectList =
271             m_correlatedPropJson.get_ref<const nlohmann::json::object_t&>();
272 
273         // Iterate through all services in the correlated properties json
274         for (const auto& l_serviceJsonObject : l_serviceJsonObjectList.items())
275         {
276             const auto& l_serviceName = l_serviceJsonObject.key();
277 
278             const nlohmann::json& l_correlatedIntfJsonObj =
279                 m_correlatedPropJson[l_serviceName]
280                     .get_ref<const nlohmann::json::object_t&>();
281 
282             // register properties changed D-Bus signal callback
283             // for all interfaces under this service.
284             std::for_each(l_correlatedIntfJsonObj.items().begin(),
285                           l_correlatedIntfJsonObj.items().end(),
286                           [this, &l_serviceName = std::as_const(l_serviceName)](
287                               const auto& i_interfaceJsonObj) {
288                               registerPropChangeCallBack(
289                                   l_serviceName, i_interfaceJsonObj.key(),
290                                   [this](sdbusplus::message_t& i_msg) {
291                                       correlatedPropChangedCallBack(i_msg);
292                                   });
293                           });
294         } // service loop
295     }
296     catch (const std::exception& l_ex)
297     {
298         EventLogger::createSyncPel(
299             EventLogger::getErrorType(l_ex), types::SeverityType::Informational,
300             __FILE__, __FUNCTION__, 0, EventLogger::getErrorMsg(l_ex),
301             std::nullopt, std::nullopt, std::nullopt, std::nullopt);
302     }
303 }
304 
305 void Listener::registerPropChangeCallBack(
306     const std::string& i_service, const std::string& i_interface,
307     std::function<void(sdbusplus::message_t& i_msg)> i_callBackFunction)
308 {
309     try
310     {
311         if (i_service.empty() || i_interface.empty())
312         {
313             throw std::runtime_error("Invalid service name or interface name");
314         }
315 
316         std::shared_ptr<sdbusplus::bus::match_t> l_matchObj =
317             std::make_unique<sdbusplus::bus::match_t>(
318                 static_cast<sdbusplus::bus_t&>(*m_asioConnection),
319                 "type='signal',member='PropertiesChanged',"
320                 "interface='org.freedesktop.DBus.Properties',"
321                 "arg0='" +
322                     i_interface + "'",
323                 i_callBackFunction);
324 
325         // save the match object in map
326         m_matchObjectMap[i_service][i_interface] = l_matchObj;
327     }
328     catch (const std::exception& l_ex)
329     {
330         throw FirmwareException(l_ex.what());
331     }
332 }
333 
334 void Listener::correlatedPropChangedCallBack(
335     sdbusplus::message_t& i_msg) noexcept
336 {
337     try
338     {
339         if (i_msg.is_method_error())
340         {
341             throw DbusException("Error in reading property change signal.");
342         }
343 
344         std::string l_interface;
345         types::PropertyMap l_propMap;
346         i_msg.read(l_interface, l_propMap);
347 
348         const std::string l_objectPath{i_msg.get_path()};
349 
350         std::string l_serviceName =
351             dbusUtility::getServiceNameFromConnectionId(i_msg.get_sender());
352 
353         if (l_serviceName.empty())
354         {
355             throw DbusException(
356                 "Failed to get service name from connection ID: " +
357                 std::string(i_msg.get_sender()));
358         }
359 
360         // if service name contains .service suffix, strip it
361         const std::size_t l_pos = l_serviceName.find(".service");
362         if (l_pos != std::string::npos)
363         {
364             l_serviceName = l_serviceName.substr(0, l_pos);
365         }
366 
367         // iterate through all properties in map
368         for (const auto& l_propertyEntry : l_propMap)
369         {
370             const std::string& l_propertyName = l_propertyEntry.first;
371             const auto& l_propertyValue = l_propertyEntry.second;
372 
373             // Use correlated JSON to find target {object path,
374             // interface,property/properties} to update
375             const auto& l_correlatedPropList = getCorrelatedProps(
376                 l_serviceName, l_objectPath, l_interface, l_propertyName);
377 
378             // update all target correlated properties
379             std::for_each(
380                 l_correlatedPropList.begin(), l_correlatedPropList.end(),
381                 [this, &l_propertyValue = std::as_const(l_propertyValue),
382                  &l_serviceName = std::as_const(l_serviceName),
383                  &l_objectPath = std::as_const(l_objectPath),
384                  &l_interface = std::as_const(l_interface),
385                  &l_propertyName = std::as_const(l_propertyName)](
386                     const auto& i_corrProperty) {
387                     if (!updateCorrelatedProperty(l_serviceName, i_corrProperty,
388                                                   l_propertyValue))
389                     {
390                         logging::logMessage(
391                             "Failed to update correlated property: " +
392                             l_serviceName + " : " +
393                             std::get<0>(i_corrProperty) + " : " +
394                             std::get<1>(i_corrProperty) + " : " +
395                             std::get<2>(i_corrProperty) + " when " +
396                             l_objectPath + " : " + l_interface + " : " +
397                             l_propertyName + " got updated.");
398                     }
399                 });
400         }
401     }
402     catch (const std::exception& l_ex)
403     {
404         EventLogger::createSyncPel(
405             EventLogger::getErrorType(l_ex), types::SeverityType::Informational,
406             __FILE__, __FUNCTION__, 0, EventLogger::getErrorMsg(l_ex),
407             std::nullopt, std::nullopt, std::nullopt, std::nullopt);
408     }
409 }
410 
411 types::DbusPropertyList Listener::getCorrelatedProps(
412     const std::string& i_serviceName, const std::string& i_objectPath,
413     const std::string& i_interface, const std::string& i_property) const
414 {
415     types::DbusPropertyList l_result;
416     try
417     {
418         if (m_correlatedPropJson.contains(i_serviceName) &&
419             m_correlatedPropJson[i_serviceName].contains(i_interface) &&
420             m_correlatedPropJson[i_serviceName][i_interface].contains(
421                 i_property))
422         {
423             const nlohmann::json& l_destinationJsonObj =
424                 m_correlatedPropJson[i_serviceName][i_interface][i_property];
425 
426             // check if any matching paths pair entry is present
427             if (l_destinationJsonObj.contains("pathsPair") &&
428                 l_destinationJsonObj["pathsPair"].contains(i_objectPath) &&
429                 l_destinationJsonObj["pathsPair"][i_objectPath].contains(
430                     "destinationInventoryPath") &&
431                 l_destinationJsonObj["pathsPair"][i_objectPath].contains(
432                     "interfaces"))
433             {
434                 // iterate through all the destination interface and property
435                 // name
436                 for (const auto& l_destinationInterfaceJsonObj :
437                      l_destinationJsonObj["pathsPair"][i_objectPath]
438                                          ["interfaces"]
439                                              .items())
440                 {
441                     // iterate through all destination inventory paths
442                     for (const auto& l_destinationInventoryPath :
443                          l_destinationJsonObj["pathsPair"][i_objectPath]
444                                              ["destinationInventoryPath"])
445                     {
446                         l_result.emplace_back(
447                             l_destinationInventoryPath,
448                             l_destinationInterfaceJsonObj.key(),
449                             l_destinationInterfaceJsonObj.value());
450                     } // destination inventory paths
451                 } // destination interfaces
452             }
453             // get the default interface, property to update
454             else if (l_destinationJsonObj.contains("defaultInterfaces"))
455             {
456                 // iterate through all default interfaces to update
457                 for (const auto& l_destinationIfcPropEntry :
458                      l_destinationJsonObj["defaultInterfaces"].items())
459                 {
460                     l_result.emplace_back(i_objectPath,
461                                           l_destinationIfcPropEntry.key(),
462                                           l_destinationIfcPropEntry.value());
463                 }
464             }
465         }
466     }
467     catch (const std::exception& l_ex)
468     {
469         throw FirmwareException(l_ex.what());
470     }
471     return l_result;
472 }
473 
474 bool Listener::updateCorrelatedProperty(
475     const std::string& i_serviceName,
476     const types::DbusPropertyEntry& i_corrProperty,
477     const types::DbusVariantType& i_propertyValue) const noexcept
478 {
479     const auto& l_destinationObjectPath{std::get<0>(i_corrProperty)};
480     const auto& l_destinationInterface{std::get<1>(i_corrProperty)};
481     const auto& l_destinationPropertyName{std::get<2>(i_corrProperty)};
482 
483     try
484     {
485         types::DbusVariantType l_valueToUpdate;
486 
487         // destination interface is ipz vpd
488         if (l_destinationInterface.find(constants::ipzVpdInf) !=
489             std::string::npos)
490         {
491             if (const auto l_val = std::get_if<std::string>(&i_propertyValue))
492             {
493                 // convert value to binary vector before updating
494                 l_valueToUpdate = commonUtility::convertToBinary(*l_val);
495             }
496             else if (const auto l_val =
497                          std::get_if<types::BinaryVector>(&i_propertyValue))
498             {
499                 l_valueToUpdate = *l_val;
500             }
501         }
502         else
503         {
504             // destination interface is not ipz vpd, assume target
505             // property type is of string type
506             if (const auto l_val =
507                     std::get_if<types::BinaryVector>(&i_propertyValue))
508             {
509                 // convert property value to string before updating
510 
511                 l_valueToUpdate = commonUtility::getPrintableValue(*l_val);
512             }
513             else if (const auto l_val =
514                          std::get_if<std::string>(&i_propertyValue))
515             {
516                 l_valueToUpdate = *l_val;
517             }
518         }
519 
520         if (i_serviceName == constants::pimServiceName)
521         {
522             return dbusUtility::callPIM(types::ObjectMap{
523                 {l_destinationObjectPath,
524                  {{l_destinationInterface,
525                    {{l_destinationPropertyName, l_valueToUpdate}}}}}});
526         }
527         else
528         {
529             return dbusUtility::writeDbusProperty(
530                 i_serviceName, l_destinationObjectPath, l_destinationInterface,
531                 l_destinationPropertyName, l_valueToUpdate);
532         }
533     }
534     catch (const std::exception& l_ex)
535     {
536         logging::logMessage(
537             "Failed to update correlated property: " + i_serviceName + " : " +
538             l_destinationObjectPath + " : " + l_destinationInterface + " : " +
539             l_destinationPropertyName + ". Error: " + std::string(l_ex.what()));
540     }
541     return false;
542 }
543 
544 } // namespace vpd
545