xref: /openbmc/dbus-sensors/src/Utils.cpp (revision 16d6f17b)
1 /*
2 // Copyright (c) 2017 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 
17 #include "Utils.hpp"
18 
19 #include "dbus-sensor_config.h"
20 
21 #include "DeviceMgmt.hpp"
22 
23 #include <boost/container/flat_map.hpp>
24 #include <sdbusplus/asio/connection.hpp>
25 #include <sdbusplus/asio/object_server.hpp>
26 #include <sdbusplus/bus/match.hpp>
27 
28 #include <charconv>
29 #include <filesystem>
30 #include <fstream>
31 #include <memory>
32 #include <regex>
33 #include <stdexcept>
34 #include <string>
35 #include <utility>
36 #include <variant>
37 #include <vector>
38 
39 namespace fs = std::filesystem;
40 
41 static bool powerStatusOn = false;
42 static bool biosHasPost = false;
43 static bool manufacturingMode = false;
44 static bool chassisStatusOn = false;
45 
46 static std::unique_ptr<sdbusplus::bus::match_t> powerMatch = nullptr;
47 static std::unique_ptr<sdbusplus::bus::match_t> postMatch = nullptr;
48 static std::unique_ptr<sdbusplus::bus::match_t> chassisMatch = nullptr;
49 
50 /**
51  * return the contents of a file
52  * @param[in] hwmonFile - the path to the file to read
53  * @return the contents of the file as a string or nullopt if the file could not
54  * be opened.
55  */
56 
57 std::optional<std::string> openAndRead(const std::string& hwmonFile)
58 {
59     std::string fileVal;
60     std::ifstream fileStream(hwmonFile);
61     if (!fileStream.is_open())
62     {
63         return std::nullopt;
64     }
65     std::getline(fileStream, fileVal);
66     return fileVal;
67 }
68 
69 /**
70  * given a hwmon temperature base name if valid return the full path else
71  * nullopt
72  * @param[in] directory - the hwmon sysfs directory
73  * @param[in] permitSet - a set of labels or hwmon basenames to permit. If this
74  * is empty then *everything* is permitted.
75  * @return a string to the full path of the file to create a temp sensor with or
76  * nullopt to indicate that no sensor should be created for this basename.
77  */
78 std::optional<std::string>
79     getFullHwmonFilePath(const std::string& directory,
80                          const std::string& hwmonBaseName,
81                          const std::set<std::string>& permitSet)
82 {
83     std::optional<std::string> result;
84     std::string filename;
85     if (permitSet.empty())
86     {
87         result = directory + "/" + hwmonBaseName + "_input";
88         return result;
89     }
90     filename = directory + "/" + hwmonBaseName + "_label";
91     auto searchVal = openAndRead(filename);
92     if (!searchVal)
93     {
94         /* if the hwmon temp doesn't have a corresponding label file
95          * then use the hwmon temperature base name
96          */
97         searchVal = hwmonBaseName;
98     }
99     if (permitSet.find(*searchVal) != permitSet.end())
100     {
101         result = directory + "/" + hwmonBaseName + "_input";
102     }
103     return result;
104 }
105 
106 /**
107  * retrieve a set of basenames and labels to allow sensor creation for.
108  * @param[in] config - a map representing the configuration for a specific
109  * device
110  * @return a set of basenames and labels to allow sensor creation for. An empty
111  * set indicates that everything is permitted.
112  */
113 std::set<std::string> getPermitSet(const SensorBaseConfigMap& config)
114 {
115     auto permitAttribute = config.find("Labels");
116     std::set<std::string> permitSet;
117     if (permitAttribute != config.end())
118     {
119         try
120         {
121             auto val =
122                 std::get<std::vector<std::string>>(permitAttribute->second);
123 
124             permitSet.insert(std::make_move_iterator(val.begin()),
125                              std::make_move_iterator(val.end()));
126         }
127         catch (const std::bad_variant_access& err)
128         {
129             std::cerr << err.what()
130                       << ":PermitList does not contain a list, wrong "
131                          "variant type.\n";
132         }
133     }
134     return permitSet;
135 }
136 
137 bool getSensorConfiguration(
138     const std::string& type,
139     const std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
140     ManagedObjectType& resp, bool useCache)
141 {
142     static ManagedObjectType managedObj;
143     std::string typeIntf = configInterfaceName(type);
144 
145     if (!useCache)
146     {
147         managedObj.clear();
148         sdbusplus::message_t getManagedObjects =
149             dbusConnection->new_method_call(
150                 entityManagerName, "/xyz/openbmc_project/inventory",
151                 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
152         try
153         {
154             sdbusplus::message_t reply =
155                 dbusConnection->call(getManagedObjects);
156             reply.read(managedObj);
157         }
158         catch (const sdbusplus::exception_t& e)
159         {
160             std::cerr << "While calling GetManagedObjects on service:"
161                       << entityManagerName << " exception name:" << e.name()
162                       << "and description:" << e.description()
163                       << " was thrown\n";
164             return false;
165         }
166     }
167     for (const auto& pathPair : managedObj)
168     {
169         for (const auto& [intf, cfg] : pathPair.second)
170         {
171             if (intf.starts_with(typeIntf))
172             {
173                 resp.emplace(pathPair);
174                 break;
175             }
176         }
177     }
178     return true;
179 }
180 
181 bool findFiles(const fs::path& dirPath, std::string_view matchString,
182                std::vector<fs::path>& foundPaths, int symlinkDepth)
183 {
184     std::error_code ec;
185     if (!fs::exists(dirPath, ec))
186     {
187         return false;
188     }
189 
190     std::vector<std::regex> matchPieces;
191 
192     size_t pos = 0;
193     std::string token;
194     // Generate the regex expressions list from the match we were given
195     while ((pos = matchString.find('/')) != std::string::npos)
196     {
197         token = matchString.substr(0, pos);
198         matchPieces.emplace_back(token);
199         matchString.remove_prefix(pos + 1);
200     }
201     matchPieces.emplace_back(std::string{matchString});
202 
203     // Check if the match string contains directories, and skip the match of
204     // subdirectory if not
205     if (matchPieces.size() <= 1)
206     {
207         std::regex search(std::string{matchString});
208         std::smatch match;
209         for (auto p = fs::recursive_directory_iterator(
210                  dirPath, fs::directory_options::follow_directory_symlink);
211              p != fs::recursive_directory_iterator(); ++p)
212         {
213             std::string path = p->path().string();
214             if (!is_directory(*p))
215             {
216                 if (std::regex_search(path, match, search))
217                 {
218                     foundPaths.emplace_back(p->path());
219                 }
220             }
221             if (p.depth() >= symlinkDepth)
222             {
223                 p.disable_recursion_pending();
224             }
225         }
226         return true;
227     }
228 
229     // The match string contains directories, verify each level of sub
230     // directories
231     for (auto p = fs::recursive_directory_iterator(
232              dirPath, fs::directory_options::follow_directory_symlink);
233          p != fs::recursive_directory_iterator(); ++p)
234     {
235         std::vector<std::regex>::iterator matchPiece = matchPieces.begin();
236         fs::path::iterator pathIt = p->path().begin();
237         for (const fs::path& dir : dirPath)
238         {
239             if (dir.empty())
240             {
241                 // When the path ends with '/', it gets am empty path
242                 // skip such case.
243                 break;
244             }
245             pathIt++;
246         }
247 
248         while (pathIt != p->path().end())
249         {
250             // Found a path deeper than match.
251             if (matchPiece == matchPieces.end())
252             {
253                 p.disable_recursion_pending();
254                 break;
255             }
256             std::smatch match;
257             std::string component = pathIt->string();
258             std::regex regexPiece(*matchPiece);
259             if (!std::regex_match(component, match, regexPiece))
260             {
261                 // path prefix doesn't match, no need to iterate further
262                 p.disable_recursion_pending();
263                 break;
264             }
265             matchPiece++;
266             pathIt++;
267         }
268 
269         if (!is_directory(*p))
270         {
271             if (matchPiece == matchPieces.end())
272             {
273                 foundPaths.emplace_back(p->path());
274             }
275         }
276 
277         if (p.depth() >= symlinkDepth)
278         {
279             p.disable_recursion_pending();
280         }
281     }
282     return true;
283 }
284 
285 bool isPowerOn(void)
286 {
287     if (!powerMatch)
288     {
289         throw std::runtime_error("Power Match Not Created");
290     }
291     return powerStatusOn;
292 }
293 
294 bool hasBiosPost(void)
295 {
296     if (!postMatch)
297     {
298         throw std::runtime_error("Post Match Not Created");
299     }
300     return biosHasPost;
301 }
302 
303 bool isChassisOn(void)
304 {
305     if (!chassisMatch)
306     {
307         throw std::runtime_error("Chassis On Match Not Created");
308     }
309     return chassisStatusOn;
310 }
311 
312 bool readingStateGood(const PowerState& powerState)
313 {
314     if (powerState == PowerState::on && !isPowerOn())
315     {
316         return false;
317     }
318     if (powerState == PowerState::biosPost && (!hasBiosPost() || !isPowerOn()))
319     {
320         return false;
321     }
322     if (powerState == PowerState::chassisOn && !isChassisOn())
323     {
324         return false;
325     }
326 
327     return true;
328 }
329 
330 static void
331     getPowerStatus(const std::shared_ptr<sdbusplus::asio::connection>& conn,
332                    size_t retries = 2)
333 {
334     conn->async_method_call(
335         [conn, retries](boost::system::error_code ec,
336                         const std::variant<std::string>& state) {
337         if (ec)
338         {
339             if (retries != 0U)
340             {
341                 auto timer = std::make_shared<boost::asio::steady_timer>(
342                     conn->get_io_context());
343                 timer->expires_after(std::chrono::seconds(15));
344                 timer->async_wait(
345                     [timer, conn, retries](boost::system::error_code) {
346                     getPowerStatus(conn, retries - 1);
347                 });
348                 return;
349             }
350 
351             // we commonly come up before power control, we'll capture the
352             // property change later
353             std::cerr << "error getting power status " << ec.message() << "\n";
354             return;
355         }
356         powerStatusOn = std::get<std::string>(state).ends_with(".Running");
357     },
358         power::busname, power::path, properties::interface, properties::get,
359         power::interface, power::property);
360 }
361 
362 static void
363     getPostStatus(const std::shared_ptr<sdbusplus::asio::connection>& conn,
364                   size_t retries = 2)
365 {
366     conn->async_method_call(
367         [conn, retries](boost::system::error_code ec,
368                         const std::variant<std::string>& state) {
369         if (ec)
370         {
371             if (retries != 0U)
372             {
373                 auto timer = std::make_shared<boost::asio::steady_timer>(
374                     conn->get_io_context());
375                 timer->expires_after(std::chrono::seconds(15));
376                 timer->async_wait(
377                     [timer, conn, retries](boost::system::error_code) {
378                     getPostStatus(conn, retries - 1);
379                 });
380                 return;
381             }
382             // we commonly come up before power control, we'll capture the
383             // property change later
384             std::cerr << "error getting post status " << ec.message() << "\n";
385             return;
386         }
387         const auto& value = std::get<std::string>(state);
388         biosHasPost = (value != "Inactive") &&
389                       (value != "xyz.openbmc_project.State.OperatingSystem."
390                                 "Status.OSStatus.Inactive");
391     },
392         post::busname, post::path, properties::interface, properties::get,
393         post::interface, post::property);
394 }
395 
396 static void
397     getChassisStatus(const std::shared_ptr<sdbusplus::asio::connection>& conn,
398                      size_t retries = 2)
399 {
400     conn->async_method_call(
401         [conn, retries](boost::system::error_code ec,
402                         const std::variant<std::string>& state) {
403         if (ec)
404         {
405             if (retries != 0U)
406             {
407                 auto timer = std::make_shared<boost::asio::steady_timer>(
408                     conn->get_io_context());
409                 timer->expires_after(std::chrono::seconds(15));
410                 timer->async_wait(
411                     [timer, conn, retries](boost::system::error_code) {
412                     getChassisStatus(conn, retries - 1);
413                 });
414                 return;
415             }
416 
417             // we commonly come up before power control, we'll capture the
418             // property change later
419             std::cerr << "error getting chassis power status " << ec.message()
420                       << "\n";
421             return;
422         }
423         chassisStatusOn = std::get<std::string>(state).ends_with(chassis::sOn);
424     },
425         chassis::busname, chassis::path, properties::interface, properties::get,
426         chassis::interface, chassis::property);
427 }
428 
429 void setupPowerMatchCallback(
430     const std::shared_ptr<sdbusplus::asio::connection>& conn,
431     std::function<void(PowerState type, bool state)>&& hostStatusCallback)
432 {
433     static boost::asio::steady_timer timer(conn->get_io_context());
434     static boost::asio::steady_timer timerChassisOn(conn->get_io_context());
435     // create a match for powergood changes, first time do a method call to
436     // cache the correct value
437     if (powerMatch)
438     {
439         return;
440     }
441 
442     powerMatch = std::make_unique<sdbusplus::bus::match_t>(
443         static_cast<sdbusplus::bus_t&>(*conn),
444         "type='signal',interface='" + std::string(properties::interface) +
445             "',path='" + std::string(power::path) + "',arg0='" +
446             std::string(power::interface) + "'",
447         [hostStatusCallback](sdbusplus::message_t& message) {
448         std::string objectName;
449         boost::container::flat_map<std::string, std::variant<std::string>>
450             values;
451         message.read(objectName, values);
452         auto findState = values.find(power::property);
453         if (findState != values.end())
454         {
455             bool on =
456                 std::get<std::string>(findState->second).ends_with(".Running");
457             if (!on)
458             {
459                 timer.cancel();
460                 powerStatusOn = false;
461                 hostStatusCallback(PowerState::on, powerStatusOn);
462                 return;
463             }
464             // on comes too quickly
465             timer.expires_after(std::chrono::seconds(10));
466             timer.async_wait(
467                 [hostStatusCallback](boost::system::error_code ec) {
468                 if (ec == boost::asio::error::operation_aborted)
469                 {
470                     return;
471                 }
472                 if (ec)
473                 {
474                     std::cerr << "Timer error " << ec.message() << "\n";
475                     return;
476                 }
477                 powerStatusOn = true;
478                 hostStatusCallback(PowerState::on, powerStatusOn);
479             });
480         }
481     });
482 
483     postMatch = std::make_unique<sdbusplus::bus::match_t>(
484         static_cast<sdbusplus::bus_t&>(*conn),
485         "type='signal',interface='" + std::string(properties::interface) +
486             "',path='" + std::string(post::path) + "',arg0='" +
487             std::string(post::interface) + "'",
488         [hostStatusCallback](sdbusplus::message_t& message) {
489         std::string objectName;
490         boost::container::flat_map<std::string, std::variant<std::string>>
491             values;
492         message.read(objectName, values);
493         auto findState = values.find(post::property);
494         if (findState != values.end())
495         {
496             auto& value = std::get<std::string>(findState->second);
497             biosHasPost = (value != "Inactive") &&
498                           (value != "xyz.openbmc_project.State.OperatingSystem."
499                                     "Status.OSStatus.Inactive");
500             hostStatusCallback(PowerState::biosPost, biosHasPost);
501         }
502     });
503 
504     chassisMatch = std::make_unique<sdbusplus::bus::match_t>(
505         static_cast<sdbusplus::bus_t&>(*conn),
506         "type='signal',interface='" + std::string(properties::interface) +
507             "',path='" + std::string(chassis::path) + "',arg0='" +
508             std::string(chassis::interface) + "'",
509         [hostStatusCallback](sdbusplus::message_t& message) {
510         std::string objectName;
511         boost::container::flat_map<std::string, std::variant<std::string>>
512             values;
513         message.read(objectName, values);
514         auto findState = values.find(chassis::property);
515         if (findState != values.end())
516         {
517             bool on = std::get<std::string>(findState->second)
518                           .ends_with(chassis::sOn);
519             if (!on)
520             {
521                 timerChassisOn.cancel();
522                 chassisStatusOn = false;
523                 hostStatusCallback(PowerState::chassisOn, chassisStatusOn);
524                 return;
525             }
526             // on comes too quickly
527             timerChassisOn.expires_after(std::chrono::seconds(10));
528             timerChassisOn.async_wait(
529                 [hostStatusCallback](boost::system::error_code ec) {
530                 if (ec == boost::asio::error::operation_aborted)
531                 {
532                     return;
533                 }
534                 if (ec)
535                 {
536                     std::cerr << "Timer error " << ec.message() << "\n";
537                     return;
538                 }
539                 chassisStatusOn = true;
540                 hostStatusCallback(PowerState::chassisOn, chassisStatusOn);
541             });
542         }
543     });
544     getPowerStatus(conn);
545     getPostStatus(conn);
546     getChassisStatus(conn);
547 }
548 
549 void setupPowerMatch(const std::shared_ptr<sdbusplus::asio::connection>& conn)
550 {
551     setupPowerMatchCallback(conn, [](PowerState, bool) {});
552 }
553 
554 // replaces limits if MinReading and MaxReading are found.
555 void findLimits(std::pair<double, double>& limits,
556                 const SensorBaseConfiguration* data)
557 {
558     if (data == nullptr)
559     {
560         return;
561     }
562     auto maxFind = data->second.find("MaxReading");
563     auto minFind = data->second.find("MinReading");
564 
565     if (minFind != data->second.end())
566     {
567         limits.first = std::visit(VariantToDoubleVisitor(), minFind->second);
568     }
569     if (maxFind != data->second.end())
570     {
571         limits.second = std::visit(VariantToDoubleVisitor(), maxFind->second);
572     }
573 }
574 
575 void createAssociation(
576     std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
577     const std::string& path)
578 {
579     if (association)
580     {
581         fs::path p(path);
582 
583         std::vector<Association> associations;
584         associations.emplace_back("chassis", "all_sensors",
585                                   p.parent_path().string());
586         association->register_property("Associations", associations);
587         association->initialize();
588     }
589 }
590 
591 void setInventoryAssociation(
592     const std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
593     const std::string& inventoryPath, const std::string& chassisPath)
594 {
595     if (association)
596     {
597         std::vector<Association> associations;
598         associations.emplace_back("inventory", "sensors", inventoryPath);
599         associations.emplace_back("chassis", "all_sensors", chassisPath);
600 
601         association->register_property("Associations", associations);
602         association->initialize();
603     }
604 }
605 
606 std::optional<std::string> findContainingChassis(std::string_view configParent,
607                                                  const GetSubTreeType& subtree)
608 {
609     // A parent that is a chassis takes precedence
610     for (const auto& [obj, services] : subtree)
611     {
612         if (obj == configParent)
613         {
614             return obj;
615         }
616     }
617 
618     // If the parent is not a chassis, the system chassis is used. This does not
619     // work if there is more than one System, but we assume there is only one
620     // today.
621     for (const auto& [obj, services] : subtree)
622     {
623         for (const auto& [service, interfaces] : services)
624         {
625             if (std::find(interfaces.begin(), interfaces.end(),
626                           "xyz.openbmc_project.Inventory.Item.System") !=
627                 interfaces.end())
628             {
629                 return obj;
630             }
631         }
632     }
633     return std::nullopt;
634 }
635 
636 void createInventoryAssoc(
637     const std::shared_ptr<sdbusplus::asio::connection>& conn,
638     const std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
639     const std::string& path)
640 {
641     if (!association)
642     {
643         return;
644     }
645 
646     constexpr auto allInterfaces = std::to_array({
647         "xyz.openbmc_project.Inventory.Item.Board",
648         "xyz.openbmc_project.Inventory.Item.Chassis",
649     });
650 
651     conn->async_method_call(
652         [association, path](const boost::system::error_code ec,
653                             const GetSubTreeType& subtree) {
654         // The parent of the config is always the inventory object, and may be
655         // the associated chassis. If the parent is not itself a chassis or
656         // board, the sensor is associated with the system chassis.
657         std::string parent = fs::path(path).parent_path().string();
658         if (ec)
659         {
660             // In case of error, set the default associations and
661             // initialize the association Interface.
662             setInventoryAssociation(association, parent, parent);
663             return;
664         }
665         setInventoryAssociation(
666             association, parent,
667             findContainingChassis(parent, subtree).value_or(parent));
668     },
669         mapper::busName, mapper::path, mapper::interface, "GetSubTree",
670         "/xyz/openbmc_project/inventory/system", 2, allInterfaces);
671 }
672 
673 std::optional<double> readFile(const std::string& thresholdFile,
674                                const double& scaleFactor)
675 {
676     std::string line;
677     std::ifstream labelFile(thresholdFile);
678     if (labelFile.good())
679     {
680         std::getline(labelFile, line);
681         labelFile.close();
682 
683         try
684         {
685             return std::stod(line) / scaleFactor;
686         }
687         catch (const std::invalid_argument&)
688         {
689             return std::nullopt;
690         }
691     }
692     return std::nullopt;
693 }
694 
695 std::optional<std::tuple<std::string, std::string, std::string>>
696     splitFileName(const fs::path& filePath)
697 {
698     if (filePath.has_filename())
699     {
700         const auto fileName = filePath.filename().string();
701 
702         size_t numberPos = std::strcspn(fileName.c_str(), "1234567890");
703         size_t itemPos = std::strcspn(fileName.c_str(), "_");
704 
705         if (numberPos > 0 && itemPos > numberPos && fileName.size() > itemPos)
706         {
707             return std::make_optional(
708                 std::make_tuple(fileName.substr(0, numberPos),
709                                 fileName.substr(numberPos, itemPos - numberPos),
710                                 fileName.substr(itemPos + 1, fileName.size())));
711         }
712     }
713     return std::nullopt;
714 }
715 
716 static void handleSpecialModeChange(const std::string& manufacturingModeStatus)
717 {
718     manufacturingMode = false;
719     if (manufacturingModeStatus == "xyz.openbmc_project.Control.Security."
720                                    "SpecialMode.Modes.Manufacturing")
721     {
722         manufacturingMode = true;
723     }
724     if (validateUnsecureFeature == 1)
725     {
726         if (manufacturingModeStatus == "xyz.openbmc_project.Control.Security."
727                                        "SpecialMode.Modes.ValidationUnsecure")
728         {
729             manufacturingMode = true;
730         }
731     }
732 }
733 
734 void setupManufacturingModeMatch(sdbusplus::asio::connection& conn)
735 {
736     namespace rules = sdbusplus::bus::match::rules;
737     static constexpr const char* specialModeInterface =
738         "xyz.openbmc_project.Security.SpecialMode";
739 
740     const std::string filterSpecialModeIntfAdd =
741         rules::interfacesAdded() +
742         rules::argNpath(0, "/xyz/openbmc_project/security/special_mode");
743     static std::unique_ptr<sdbusplus::bus::match_t> specialModeIntfMatch =
744         std::make_unique<sdbusplus::bus::match_t>(
745             conn, filterSpecialModeIntfAdd, [](sdbusplus::message_t& m) {
746         sdbusplus::message::object_path path;
747         using PropertyMap =
748             boost::container::flat_map<std::string, std::variant<std::string>>;
749         boost::container::flat_map<std::string, PropertyMap> interfaceAdded;
750         m.read(path, interfaceAdded);
751         auto intfItr = interfaceAdded.find(specialModeInterface);
752         if (intfItr == interfaceAdded.end())
753         {
754             return;
755         }
756         PropertyMap& propertyList = intfItr->second;
757         auto itr = propertyList.find("SpecialMode");
758         if (itr == propertyList.end())
759         {
760             std::cerr << "error getting  SpecialMode property "
761                       << "\n";
762             return;
763         }
764         auto* manufacturingModeStatus = std::get_if<std::string>(&itr->second);
765         handleSpecialModeChange(*manufacturingModeStatus);
766     });
767 
768     const std::string filterSpecialModeChange =
769         rules::type::signal() + rules::member("PropertiesChanged") +
770         rules::interface("org.freedesktop.DBus.Properties") +
771         rules::argN(0, specialModeInterface);
772     static std::unique_ptr<sdbusplus::bus::match_t> specialModeChangeMatch =
773         std::make_unique<sdbusplus::bus::match_t>(conn, filterSpecialModeChange,
774                                                   [](sdbusplus::message_t& m) {
775         std::string interfaceName;
776         boost::container::flat_map<std::string, std::variant<std::string>>
777             propertiesChanged;
778 
779         m.read(interfaceName, propertiesChanged);
780         auto itr = propertiesChanged.find("SpecialMode");
781         if (itr == propertiesChanged.end())
782         {
783             return;
784         }
785         auto* manufacturingModeStatus = std::get_if<std::string>(&itr->second);
786         handleSpecialModeChange(*manufacturingModeStatus);
787     });
788 
789     conn.async_method_call(
790         [](const boost::system::error_code ec,
791            const std::variant<std::string>& getManufactMode) {
792         if (ec)
793         {
794             std::cerr << "error getting  SpecialMode status " << ec.message()
795                       << "\n";
796             return;
797         }
798         const auto* manufacturingModeStatus =
799             std::get_if<std::string>(&getManufactMode);
800         handleSpecialModeChange(*manufacturingModeStatus);
801     },
802         "xyz.openbmc_project.SpecialMode",
803         "/xyz/openbmc_project/security/special_mode",
804         "org.freedesktop.DBus.Properties", "Get", specialModeInterface,
805         "SpecialMode");
806 }
807 
808 bool getManufacturingMode()
809 {
810     return manufacturingMode;
811 }
812 
813 std::vector<std::unique_ptr<sdbusplus::bus::match_t>>
814     setupPropertiesChangedMatches(
815         sdbusplus::asio::connection& bus, std::span<const char* const> types,
816         const std::function<void(sdbusplus::message_t&)>& handler)
817 {
818     std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches;
819     for (const char* type : types)
820     {
821         auto match = std::make_unique<sdbusplus::bus::match_t>(
822             static_cast<sdbusplus::bus_t&>(bus),
823             "type='signal',member='PropertiesChanged',path_namespace='" +
824                 std::string(inventoryPath) + "',arg0namespace='" +
825                 configInterfaceName(type) + "'",
826             handler);
827         matches.emplace_back(std::move(match));
828     }
829     return matches;
830 }
831 
832 std::vector<std::unique_ptr<sdbusplus::bus::match_t>>
833     setupPropertiesChangedMatches(
834         sdbusplus::asio::connection& bus, const I2CDeviceTypeMap& typeMap,
835         const std::function<void(sdbusplus::message_t&)>& handler)
836 {
837     std::vector<const char*> types;
838     types.reserve(typeMap.size());
839     for (const auto& [type, dt] : typeMap)
840     {
841         types.push_back(type.data());
842     }
843     return setupPropertiesChangedMatches(bus, {types}, handler);
844 }
845