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