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