xref: /openbmc/dbus-sensors/src/Utils.cpp (revision bb1faac3)
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)
141 {
142     return getSensorConfiguration(type, dbusConnection, resp, false);
143 }
144 
145 bool getSensorConfiguration(
146     const std::string& type,
147     const std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
148     ManagedObjectType& resp, bool useCache)
149 {
150     static ManagedObjectType managedObj;
151     std::string typeIntf = configInterfaceName(type);
152 
153     if (!useCache)
154     {
155         managedObj.clear();
156         sdbusplus::message_t getManagedObjects =
157             dbusConnection->new_method_call(
158                 entityManagerName, "/xyz/openbmc_project/inventory",
159                 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
160         try
161         {
162             sdbusplus::message_t reply =
163                 dbusConnection->call(getManagedObjects);
164             reply.read(managedObj);
165         }
166         catch (const sdbusplus::exception_t& e)
167         {
168             std::cerr << "While calling GetManagedObjects on service:"
169                       << entityManagerName << " exception name:" << e.name()
170                       << "and description:" << e.description()
171                       << " was thrown\n";
172             return false;
173         }
174     }
175     for (const auto& pathPair : managedObj)
176     {
177         for (const auto& [intf, cfg] : pathPair.second)
178         {
179             if (intf.starts_with(typeIntf))
180             {
181                 resp.emplace(pathPair);
182                 break;
183             }
184         }
185     }
186     return true;
187 }
188 
189 bool findFiles(const fs::path& dirPath, std::string_view matchString,
190                std::vector<fs::path>& foundPaths, int symlinkDepth)
191 {
192     std::error_code ec;
193     if (!fs::exists(dirPath, ec))
194     {
195         return false;
196     }
197 
198     std::vector<std::regex> matchPieces;
199 
200     size_t pos = 0;
201     std::string token;
202     // Generate the regex expressions list from the match we were given
203     while ((pos = matchString.find('/')) != std::string::npos)
204     {
205         token = matchString.substr(0, pos);
206         matchPieces.emplace_back(token);
207         matchString.remove_prefix(pos + 1);
208     }
209     matchPieces.emplace_back(std::string{matchString});
210 
211     // Check if the match string contains directories, and skip the match of
212     // subdirectory if not
213     if (matchPieces.size() <= 1)
214     {
215         std::regex search(std::string{matchString});
216         std::smatch match;
217         for (auto p = fs::recursive_directory_iterator(
218                  dirPath, fs::directory_options::follow_directory_symlink);
219              p != fs::recursive_directory_iterator(); ++p)
220         {
221             std::string path = p->path().string();
222             if (!is_directory(*p))
223             {
224                 if (std::regex_search(path, match, search))
225                 {
226                     foundPaths.emplace_back(p->path());
227                 }
228             }
229             if (p.depth() >= symlinkDepth)
230             {
231                 p.disable_recursion_pending();
232             }
233         }
234         return true;
235     }
236 
237     // The match string contains directories, verify each level of sub
238     // directories
239     for (auto p = fs::recursive_directory_iterator(
240              dirPath, fs::directory_options::follow_directory_symlink);
241          p != fs::recursive_directory_iterator(); ++p)
242     {
243         std::vector<std::regex>::iterator matchPiece = matchPieces.begin();
244         fs::path::iterator pathIt = p->path().begin();
245         for (const fs::path& dir : dirPath)
246         {
247             if (dir.empty())
248             {
249                 // When the path ends with '/', it gets am empty path
250                 // skip such case.
251                 break;
252             }
253             pathIt++;
254         }
255 
256         while (pathIt != p->path().end())
257         {
258             // Found a path deeper than match.
259             if (matchPiece == matchPieces.end())
260             {
261                 p.disable_recursion_pending();
262                 break;
263             }
264             std::smatch match;
265             std::string component = pathIt->string();
266             std::regex regexPiece(*matchPiece);
267             if (!std::regex_match(component, match, regexPiece))
268             {
269                 // path prefix doesn't match, no need to iterate further
270                 p.disable_recursion_pending();
271                 break;
272             }
273             matchPiece++;
274             pathIt++;
275         }
276 
277         if (!is_directory(*p))
278         {
279             if (matchPiece == matchPieces.end())
280             {
281                 foundPaths.emplace_back(p->path());
282             }
283         }
284 
285         if (p.depth() >= symlinkDepth)
286         {
287             p.disable_recursion_pending();
288         }
289     }
290     return true;
291 }
292 
293 bool isPowerOn(void)
294 {
295     if (!powerMatch)
296     {
297         throw std::runtime_error("Power Match Not Created");
298     }
299     return powerStatusOn;
300 }
301 
302 bool hasBiosPost(void)
303 {
304     if (!postMatch)
305     {
306         throw std::runtime_error("Post Match Not Created");
307     }
308     return biosHasPost;
309 }
310 
311 bool isChassisOn(void)
312 {
313     if (!chassisMatch)
314     {
315         throw std::runtime_error("Chassis On Match Not Created");
316     }
317     return chassisStatusOn;
318 }
319 
320 bool readingStateGood(const PowerState& powerState)
321 {
322     if (powerState == PowerState::on && !isPowerOn())
323     {
324         return false;
325     }
326     if (powerState == PowerState::biosPost && (!hasBiosPost() || !isPowerOn()))
327     {
328         return false;
329     }
330     if (powerState == PowerState::chassisOn && !isChassisOn())
331     {
332         return false;
333     }
334 
335     return true;
336 }
337 
338 static void
339     getPowerStatus(const std::shared_ptr<sdbusplus::asio::connection>& conn,
340                    size_t retries = 2)
341 {
342     conn->async_method_call(
343         [conn, retries](boost::system::error_code ec,
344                         const std::variant<std::string>& state) {
345         if (ec)
346         {
347             if (retries != 0U)
348             {
349                 auto timer = std::make_shared<boost::asio::steady_timer>(
350                     conn->get_io_context());
351                 timer->expires_after(std::chrono::seconds(15));
352                 timer->async_wait(
353                     [timer, conn, retries](boost::system::error_code) {
354                     getPowerStatus(conn, retries - 1);
355                 });
356                 return;
357             }
358 
359             // we commonly come up before power control, we'll capture the
360             // property change later
361             std::cerr << "error getting power status " << ec.message() << "\n";
362             return;
363         }
364         powerStatusOn = std::get<std::string>(state).ends_with(".Running");
365     },
366         power::busname, power::path, properties::interface, properties::get,
367         power::interface, power::property);
368 }
369 
370 static void
371     getPostStatus(const std::shared_ptr<sdbusplus::asio::connection>& conn,
372                   size_t retries = 2)
373 {
374     conn->async_method_call(
375         [conn, retries](boost::system::error_code ec,
376                         const std::variant<std::string>& state) {
377         if (ec)
378         {
379             if (retries != 0U)
380             {
381                 auto timer = std::make_shared<boost::asio::steady_timer>(
382                     conn->get_io_context());
383                 timer->expires_after(std::chrono::seconds(15));
384                 timer->async_wait(
385                     [timer, conn, retries](boost::system::error_code) {
386                     getPostStatus(conn, retries - 1);
387                 });
388                 return;
389             }
390             // we commonly come up before power control, we'll capture the
391             // property change later
392             std::cerr << "error getting post status " << ec.message() << "\n";
393             return;
394         }
395         const auto& value = std::get<std::string>(state);
396         biosHasPost = (value != "Inactive") &&
397                       (value != "xyz.openbmc_project.State.OperatingSystem."
398                                 "Status.OSStatus.Inactive");
399     },
400         post::busname, post::path, properties::interface, properties::get,
401         post::interface, post::property);
402 }
403 
404 static void
405     getChassisStatus(const std::shared_ptr<sdbusplus::asio::connection>& conn,
406                      size_t retries = 2)
407 {
408     conn->async_method_call(
409         [conn, retries](boost::system::error_code ec,
410                         const std::variant<std::string>& state) {
411         if (ec)
412         {
413             if (retries != 0U)
414             {
415                 auto timer = std::make_shared<boost::asio::steady_timer>(
416                     conn->get_io_context());
417                 timer->expires_after(std::chrono::seconds(15));
418                 timer->async_wait(
419                     [timer, conn, retries](boost::system::error_code) {
420                     getChassisStatus(conn, retries - 1);
421                 });
422                 return;
423             }
424 
425             // we commonly come up before power control, we'll capture the
426             // property change later
427             std::cerr << "error getting chassis power status " << ec.message()
428                       << "\n";
429             return;
430         }
431         chassisStatusOn = std::get<std::string>(state).ends_with(chassis::sOn);
432     },
433         chassis::busname, chassis::path, properties::interface, properties::get,
434         chassis::interface, chassis::property);
435 }
436 
437 void setupPowerMatchCallback(
438     const std::shared_ptr<sdbusplus::asio::connection>& conn,
439     std::function<void(PowerState type, bool state)>&& hostStatusCallback)
440 {
441     static boost::asio::steady_timer timer(conn->get_io_context());
442     static boost::asio::steady_timer timerChassisOn(conn->get_io_context());
443     // create a match for powergood changes, first time do a method call to
444     // cache the correct value
445     if (powerMatch)
446     {
447         return;
448     }
449 
450     powerMatch = std::make_unique<sdbusplus::bus::match_t>(
451         static_cast<sdbusplus::bus_t&>(*conn),
452         "type='signal',interface='" + std::string(properties::interface) +
453             "',path='" + std::string(power::path) + "',arg0='" +
454             std::string(power::interface) + "'",
455         [hostStatusCallback](sdbusplus::message_t& message) {
456         std::string objectName;
457         boost::container::flat_map<std::string, std::variant<std::string>>
458             values;
459         message.read(objectName, values);
460         auto findState = values.find(power::property);
461         if (findState != values.end())
462         {
463             bool on =
464                 std::get<std::string>(findState->second).ends_with(".Running");
465             if (!on)
466             {
467                 timer.cancel();
468                 powerStatusOn = false;
469                 hostStatusCallback(PowerState::on, powerStatusOn);
470                 return;
471             }
472             // on comes too quickly
473             timer.expires_after(std::chrono::seconds(10));
474             timer.async_wait(
475                 [hostStatusCallback](boost::system::error_code ec) {
476                 if (ec == boost::asio::error::operation_aborted)
477                 {
478                     return;
479                 }
480                 if (ec)
481                 {
482                     std::cerr << "Timer error " << ec.message() << "\n";
483                     return;
484                 }
485                 powerStatusOn = true;
486                 hostStatusCallback(PowerState::on, powerStatusOn);
487             });
488         }
489     });
490 
491     postMatch = std::make_unique<sdbusplus::bus::match_t>(
492         static_cast<sdbusplus::bus_t&>(*conn),
493         "type='signal',interface='" + std::string(properties::interface) +
494             "',path='" + std::string(post::path) + "',arg0='" +
495             std::string(post::interface) + "'",
496         [hostStatusCallback](sdbusplus::message_t& message) {
497         std::string objectName;
498         boost::container::flat_map<std::string, std::variant<std::string>>
499             values;
500         message.read(objectName, values);
501         auto findState = values.find(post::property);
502         if (findState != values.end())
503         {
504             auto& value = std::get<std::string>(findState->second);
505             biosHasPost = (value != "Inactive") &&
506                           (value != "xyz.openbmc_project.State.OperatingSystem."
507                                     "Status.OSStatus.Inactive");
508             hostStatusCallback(PowerState::biosPost, biosHasPost);
509         }
510     });
511 
512     chassisMatch = std::make_unique<sdbusplus::bus::match_t>(
513         static_cast<sdbusplus::bus_t&>(*conn),
514         "type='signal',interface='" + std::string(properties::interface) +
515             "',path='" + std::string(chassis::path) + "',arg0='" +
516             std::string(chassis::interface) + "'",
517         [hostStatusCallback](sdbusplus::message_t& message) {
518         std::string objectName;
519         boost::container::flat_map<std::string, std::variant<std::string>>
520             values;
521         message.read(objectName, values);
522         auto findState = values.find(chassis::property);
523         if (findState != values.end())
524         {
525             bool on = std::get<std::string>(findState->second)
526                           .ends_with(chassis::sOn);
527             if (!on)
528             {
529                 timerChassisOn.cancel();
530                 chassisStatusOn = false;
531                 hostStatusCallback(PowerState::chassisOn, chassisStatusOn);
532                 return;
533             }
534             // on comes too quickly
535             timerChassisOn.expires_after(std::chrono::seconds(10));
536             timerChassisOn.async_wait(
537                 [hostStatusCallback](boost::system::error_code ec) {
538                 if (ec == boost::asio::error::operation_aborted)
539                 {
540                     return;
541                 }
542                 if (ec)
543                 {
544                     std::cerr << "Timer error " << ec.message() << "\n";
545                     return;
546                 }
547                 chassisStatusOn = true;
548                 hostStatusCallback(PowerState::chassisOn, chassisStatusOn);
549             });
550         }
551     });
552     getPowerStatus(conn);
553     getPostStatus(conn);
554     getChassisStatus(conn);
555 }
556 
557 void setupPowerMatch(const std::shared_ptr<sdbusplus::asio::connection>& conn)
558 {
559     setupPowerMatchCallback(conn, [](PowerState, bool) {});
560 }
561 
562 // replaces limits if MinReading and MaxReading are found.
563 void findLimits(std::pair<double, double>& limits,
564                 const SensorBaseConfiguration* data)
565 {
566     if (data == nullptr)
567     {
568         return;
569     }
570     auto maxFind = data->second.find("MaxReading");
571     auto minFind = data->second.find("MinReading");
572 
573     if (minFind != data->second.end())
574     {
575         limits.first = std::visit(VariantToDoubleVisitor(), minFind->second);
576     }
577     if (maxFind != data->second.end())
578     {
579         limits.second = std::visit(VariantToDoubleVisitor(), maxFind->second);
580     }
581 }
582 
583 void createAssociation(
584     std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
585     const std::string& path)
586 {
587     if (association)
588     {
589         fs::path p(path);
590 
591         std::vector<Association> associations;
592         associations.emplace_back("chassis", "all_sensors",
593                                   p.parent_path().string());
594         association->register_property("Associations", associations);
595         association->initialize();
596     }
597 }
598 
599 void setInventoryAssociation(
600     const std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
601     const std::string& inventoryPath, const std::string& chassisPath)
602 {
603     if (association)
604     {
605         std::vector<Association> associations;
606         associations.emplace_back("inventory", "sensors", inventoryPath);
607         associations.emplace_back("chassis", "all_sensors", chassisPath);
608 
609         association->register_property("Associations", associations);
610         association->initialize();
611     }
612 }
613 
614 std::optional<std::string> findContainingChassis(std::string_view configParent,
615                                                  const GetSubTreeType& subtree)
616 {
617     // A parent that is a chassis takes precedence
618     for (const auto& [obj, services] : subtree)
619     {
620         if (obj == configParent)
621         {
622             return obj;
623         }
624     }
625 
626     // If the parent is not a chassis, the system chassis is used. This does not
627     // work if there is more than one System, but we assume there is only one
628     // today.
629     for (const auto& [obj, services] : subtree)
630     {
631         for (const auto& [service, interfaces] : services)
632         {
633             if (std::find(interfaces.begin(), interfaces.end(),
634                           "xyz.openbmc_project.Inventory.Item.System") !=
635                 interfaces.end())
636             {
637                 return obj;
638             }
639         }
640     }
641     return std::nullopt;
642 }
643 
644 void createInventoryAssoc(
645     const std::shared_ptr<sdbusplus::asio::connection>& conn,
646     const std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
647     const std::string& path)
648 {
649     if (!association)
650     {
651         return;
652     }
653 
654     constexpr auto allInterfaces = std::to_array({
655         "xyz.openbmc_project.Inventory.Item.Board",
656         "xyz.openbmc_project.Inventory.Item.Chassis",
657     });
658 
659     conn->async_method_call(
660         [association, path](const boost::system::error_code ec,
661                             const GetSubTreeType& subtree) {
662         // The parent of the config is always the inventory object, and may be
663         // the associated chassis. If the parent is not itself a chassis or
664         // board, the sensor is associated with the system chassis.
665         std::string parent = fs::path(path).parent_path().string();
666         if (ec)
667         {
668             // In case of error, set the default associations and
669             // initialize the association Interface.
670             setInventoryAssociation(association, parent, parent);
671             return;
672         }
673         setInventoryAssociation(
674             association, parent,
675             findContainingChassis(parent, subtree).value_or(parent));
676     },
677         mapper::busName, mapper::path, mapper::interface, "GetSubTree",
678         "/xyz/openbmc_project/inventory/system", 2, allInterfaces);
679 }
680 
681 std::optional<double> readFile(const std::string& thresholdFile,
682                                const double& scaleFactor)
683 {
684     std::string line;
685     std::ifstream labelFile(thresholdFile);
686     if (labelFile.good())
687     {
688         std::getline(labelFile, line);
689         labelFile.close();
690 
691         try
692         {
693             return std::stod(line) / scaleFactor;
694         }
695         catch (const std::invalid_argument&)
696         {
697             return std::nullopt;
698         }
699     }
700     return std::nullopt;
701 }
702 
703 std::optional<std::tuple<std::string, std::string, std::string>>
704     splitFileName(const fs::path& filePath)
705 {
706     if (filePath.has_filename())
707     {
708         const auto fileName = filePath.filename().string();
709 
710         size_t numberPos = std::strcspn(fileName.c_str(), "1234567890");
711         size_t itemPos = std::strcspn(fileName.c_str(), "_");
712 
713         if (numberPos > 0 && itemPos > numberPos && fileName.size() > itemPos)
714         {
715             return std::make_optional(
716                 std::make_tuple(fileName.substr(0, numberPos),
717                                 fileName.substr(numberPos, itemPos - numberPos),
718                                 fileName.substr(itemPos + 1, fileName.size())));
719         }
720     }
721     return std::nullopt;
722 }
723 
724 static void handleSpecialModeChange(const std::string& manufacturingModeStatus)
725 {
726     manufacturingMode = false;
727     if (manufacturingModeStatus == "xyz.openbmc_project.Control.Security."
728                                    "SpecialMode.Modes.Manufacturing")
729     {
730         manufacturingMode = true;
731     }
732     if (validateUnsecureFeature == 1)
733     {
734         if (manufacturingModeStatus == "xyz.openbmc_project.Control.Security."
735                                        "SpecialMode.Modes.ValidationUnsecure")
736         {
737             manufacturingMode = true;
738         }
739     }
740 }
741 
742 void setupManufacturingModeMatch(sdbusplus::asio::connection& conn)
743 {
744     namespace rules = sdbusplus::bus::match::rules;
745     static constexpr const char* specialModeInterface =
746         "xyz.openbmc_project.Security.SpecialMode";
747 
748     const std::string filterSpecialModeIntfAdd =
749         rules::interfacesAdded() +
750         rules::argNpath(0, "/xyz/openbmc_project/security/special_mode");
751     static std::unique_ptr<sdbusplus::bus::match_t> specialModeIntfMatch =
752         std::make_unique<sdbusplus::bus::match_t>(
753             conn, filterSpecialModeIntfAdd, [](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