xref: /openbmc/dbus-sensors/src/Utils.cpp (revision 9142343b)
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()
286 {
287     if (!powerMatch)
288     {
289         throw std::runtime_error("Power Match Not Created");
290     }
291     return powerStatusOn;
292 }
293 
294 bool hasBiosPost()
295 {
296     if (!postMatch)
297     {
298         throw std::runtime_error("Post Match Not Created");
299     }
300     return biosHasPost;
301 }
302 
303 bool isChassisOn()
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 = std::move(hostStatusCallback)](
510             sdbusplus::message_t& message) {
511         std::string objectName;
512         boost::container::flat_map<std::string, std::variant<std::string>>
513             values;
514         message.read(objectName, values);
515         auto findState = values.find(chassis::property);
516         if (findState != values.end())
517         {
518             bool on = std::get<std::string>(findState->second)
519                           .ends_with(chassis::sOn);
520             if (!on)
521             {
522                 timerChassisOn.cancel();
523                 chassisStatusOn = false;
524                 hostStatusCallback(PowerState::chassisOn, chassisStatusOn);
525                 return;
526             }
527             // on comes too quickly
528             timerChassisOn.expires_after(std::chrono::seconds(10));
529             timerChassisOn.async_wait(
530                 [hostStatusCallback](boost::system::error_code ec) {
531                 if (ec == boost::asio::error::operation_aborted)
532                 {
533                     return;
534                 }
535                 if (ec)
536                 {
537                     std::cerr << "Timer error " << ec.message() << "\n";
538                     return;
539                 }
540                 chassisStatusOn = true;
541                 hostStatusCallback(PowerState::chassisOn, chassisStatusOn);
542             });
543         }
544     });
545     getPowerStatus(conn);
546     getPostStatus(conn);
547     getChassisStatus(conn);
548 }
549 
550 void setupPowerMatch(const std::shared_ptr<sdbusplus::asio::connection>& conn)
551 {
552     setupPowerMatchCallback(conn, [](PowerState, bool) {});
553 }
554 
555 // replaces limits if MinReading and MaxReading are found.
556 void findLimits(std::pair<double, double>& limits,
557                 const SensorBaseConfiguration* data)
558 {
559     if (data == nullptr)
560     {
561         return;
562     }
563     auto maxFind = data->second.find("MaxReading");
564     auto minFind = data->second.find("MinReading");
565 
566     if (minFind != data->second.end())
567     {
568         limits.first = std::visit(VariantToDoubleVisitor(), minFind->second);
569     }
570     if (maxFind != data->second.end())
571     {
572         limits.second = std::visit(VariantToDoubleVisitor(), maxFind->second);
573     }
574 }
575 
576 void createAssociation(
577     std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
578     const std::string& path)
579 {
580     if (association)
581     {
582         fs::path p(path);
583 
584         std::vector<Association> associations;
585         associations.emplace_back("chassis", "all_sensors",
586                                   p.parent_path().string());
587         association->register_property("Associations", associations);
588         association->initialize();
589     }
590 }
591 
592 void setInventoryAssociation(
593     const std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
594     const std::string& inventoryPath, const std::string& chassisPath)
595 {
596     if (association)
597     {
598         std::vector<Association> associations;
599         associations.emplace_back("inventory", "sensors", inventoryPath);
600         associations.emplace_back("chassis", "all_sensors", chassisPath);
601 
602         association->register_property("Associations", associations);
603         association->initialize();
604     }
605 }
606 
607 std::optional<std::string> findContainingChassis(std::string_view configParent,
608                                                  const GetSubTreeType& subtree)
609 {
610     // A parent that is a chassis takes precedence
611     for (const auto& [obj, services] : subtree)
612     {
613         if (obj == configParent)
614         {
615             return obj;
616         }
617     }
618 
619     // If the parent is not a chassis, the system chassis is used. This does not
620     // work if there is more than one System, but we assume there is only one
621     // today.
622     for (const auto& [obj, services] : subtree)
623     {
624         for (const auto& [service, interfaces] : services)
625         {
626             if (std::find(interfaces.begin(), interfaces.end(),
627                           "xyz.openbmc_project.Inventory.Item.System") !=
628                 interfaces.end())
629             {
630                 return obj;
631             }
632         }
633     }
634     return std::nullopt;
635 }
636 
637 void createInventoryAssoc(
638     const std::shared_ptr<sdbusplus::asio::connection>& conn,
639     const std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
640     const std::string& path)
641 {
642     if (!association)
643     {
644         return;
645     }
646 
647     constexpr auto allInterfaces = std::to_array({
648         "xyz.openbmc_project.Inventory.Item.Board",
649         "xyz.openbmc_project.Inventory.Item.Chassis",
650     });
651 
652     conn->async_method_call(
653         [association, path](const boost::system::error_code ec,
654                             const GetSubTreeType& subtree) {
655         // The parent of the config is always the inventory object, and may be
656         // the associated chassis. If the parent is not itself a chassis or
657         // board, the sensor is associated with the system chassis.
658         std::string parent = fs::path(path).parent_path().string();
659         if (ec)
660         {
661             // In case of error, set the default associations and
662             // initialize the association Interface.
663             setInventoryAssociation(association, parent, parent);
664             return;
665         }
666         setInventoryAssociation(
667             association, parent,
668             findContainingChassis(parent, subtree).value_or(parent));
669     },
670         mapper::busName, mapper::path, mapper::interface, "GetSubTree",
671         "/xyz/openbmc_project/inventory/system", 2, allInterfaces);
672 }
673 
674 std::optional<double> readFile(const std::string& thresholdFile,
675                                const double& scaleFactor)
676 {
677     std::string line;
678     std::ifstream labelFile(thresholdFile);
679     if (labelFile.good())
680     {
681         std::getline(labelFile, line);
682         labelFile.close();
683 
684         try
685         {
686             return std::stod(line) / scaleFactor;
687         }
688         catch (const std::invalid_argument&)
689         {
690             return std::nullopt;
691         }
692     }
693     return std::nullopt;
694 }
695 
696 std::optional<std::tuple<std::string, std::string, std::string>>
697     splitFileName(const fs::path& filePath)
698 {
699     if (filePath.has_filename())
700     {
701         const auto fileName = filePath.filename().string();
702 
703         size_t numberPos = std::strcspn(fileName.c_str(), "1234567890");
704         size_t itemPos = std::strcspn(fileName.c_str(), "_");
705 
706         if (numberPos > 0 && itemPos > numberPos && fileName.size() > itemPos)
707         {
708             return std::make_optional(
709                 std::make_tuple(fileName.substr(0, numberPos),
710                                 fileName.substr(numberPos, itemPos - numberPos),
711                                 fileName.substr(itemPos + 1, fileName.size())));
712         }
713     }
714     return std::nullopt;
715 }
716 
717 static void handleSpecialModeChange(const std::string& manufacturingModeStatus)
718 {
719     manufacturingMode = false;
720     if (manufacturingModeStatus == "xyz.openbmc_project.Control.Security."
721                                    "SpecialMode.Modes.Manufacturing")
722     {
723         manufacturingMode = true;
724     }
725     if (validateUnsecureFeature == 1)
726     {
727         if (manufacturingModeStatus == "xyz.openbmc_project.Control.Security."
728                                        "SpecialMode.Modes.ValidationUnsecure")
729         {
730             manufacturingMode = true;
731         }
732     }
733 }
734 
735 void setupManufacturingModeMatch(sdbusplus::asio::connection& conn)
736 {
737     namespace rules = sdbusplus::bus::match::rules;
738     static constexpr const char* specialModeInterface =
739         "xyz.openbmc_project.Security.SpecialMode";
740 
741     const std::string filterSpecialModeIntfAdd =
742         rules::interfacesAdded() +
743         rules::argNpath(0, "/xyz/openbmc_project/security/special_mode");
744     static std::unique_ptr<sdbusplus::bus::match_t> specialModeIntfMatch =
745         std::make_unique<sdbusplus::bus::match_t>(
746             conn, filterSpecialModeIntfAdd, [](sdbusplus::message_t& m) {
747         sdbusplus::message::object_path path;
748         using PropertyMap =
749             boost::container::flat_map<std::string, std::variant<std::string>>;
750         boost::container::flat_map<std::string, PropertyMap> interfaceAdded;
751         m.read(path, interfaceAdded);
752         auto intfItr = interfaceAdded.find(specialModeInterface);
753         if (intfItr == interfaceAdded.end())
754         {
755             return;
756         }
757         PropertyMap& propertyList = intfItr->second;
758         auto itr = propertyList.find("SpecialMode");
759         if (itr == propertyList.end())
760         {
761             std::cerr << "error getting  SpecialMode property "
762                       << "\n";
763             return;
764         }
765         auto* manufacturingModeStatus = std::get_if<std::string>(&itr->second);
766         handleSpecialModeChange(*manufacturingModeStatus);
767     });
768 
769     const std::string filterSpecialModeChange =
770         rules::type::signal() + rules::member("PropertiesChanged") +
771         rules::interface("org.freedesktop.DBus.Properties") +
772         rules::argN(0, specialModeInterface);
773     static std::unique_ptr<sdbusplus::bus::match_t> specialModeChangeMatch =
774         std::make_unique<sdbusplus::bus::match_t>(conn, filterSpecialModeChange,
775                                                   [](sdbusplus::message_t& m) {
776         std::string interfaceName;
777         boost::container::flat_map<std::string, std::variant<std::string>>
778             propertiesChanged;
779 
780         m.read(interfaceName, propertiesChanged);
781         auto itr = propertiesChanged.find("SpecialMode");
782         if (itr == propertiesChanged.end())
783         {
784             return;
785         }
786         auto* manufacturingModeStatus = std::get_if<std::string>(&itr->second);
787         handleSpecialModeChange(*manufacturingModeStatus);
788     });
789 
790     conn.async_method_call(
791         [](const boost::system::error_code ec,
792            const std::variant<std::string>& getManufactMode) {
793         if (ec)
794         {
795             std::cerr << "error getting  SpecialMode status " << ec.message()
796                       << "\n";
797             return;
798         }
799         const auto* manufacturingModeStatus =
800             std::get_if<std::string>(&getManufactMode);
801         handleSpecialModeChange(*manufacturingModeStatus);
802     },
803         "xyz.openbmc_project.SpecialMode",
804         "/xyz/openbmc_project/security/special_mode",
805         "org.freedesktop.DBus.Properties", "Get", specialModeInterface,
806         "SpecialMode");
807 }
808 
809 bool getManufacturingMode()
810 {
811     return manufacturingMode;
812 }
813 
814 std::vector<std::unique_ptr<sdbusplus::bus::match_t>>
815     setupPropertiesChangedMatches(
816         sdbusplus::asio::connection& bus, std::span<const char* const> types,
817         const std::function<void(sdbusplus::message_t&)>& handler)
818 {
819     std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches;
820     for (const char* type : types)
821     {
822         auto match = std::make_unique<sdbusplus::bus::match_t>(
823             static_cast<sdbusplus::bus_t&>(bus),
824             "type='signal',member='PropertiesChanged',path_namespace='" +
825                 std::string(inventoryPath) + "',arg0namespace='" +
826                 configInterfaceName(type) + "'",
827             handler);
828         matches.emplace_back(std::move(match));
829     }
830     return matches;
831 }
832 
833 std::vector<std::unique_ptr<sdbusplus::bus::match_t>>
834     setupPropertiesChangedMatches(
835         sdbusplus::asio::connection& bus, const I2CDeviceTypeMap& typeMap,
836         const std::function<void(sdbusplus::message_t&)>& handler)
837 {
838     std::vector<const char*> types;
839     types.reserve(typeMap.size());
840     for (const auto& [type, dt] : typeMap)
841     {
842         types.push_back(type.data());
843     }
844     return setupPropertiesChangedMatches(bus, {types}, handler);
845 }
846