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