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