xref: /openbmc/dbus-sensors/src/Utils.cpp (revision 255da6b4)
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 setupPowerMatchCallback(
399     const std::shared_ptr<sdbusplus::asio::connection>& conn,
400     std::function<void(PowerState type, bool state)>&& hostStatusCallback)
401 {
402     static boost::asio::steady_timer timer(conn->get_io_context());
403     // create a match for powergood changes, first time do a method call to
404     // cache the correct value
405     if (powerMatch)
406     {
407         return;
408     }
409 
410     powerMatch = std::make_unique<sdbusplus::bus::match_t>(
411         static_cast<sdbusplus::bus_t&>(*conn),
412         "type='signal',interface='" + std::string(properties::interface) +
413             "',path='" + std::string(power::path) + "',arg0='" +
414             std::string(power::interface) + "'",
415         [hostStatusCallback](sdbusplus::message_t& message) {
416         std::string objectName;
417         boost::container::flat_map<std::string, std::variant<std::string>>
418             values;
419         message.read(objectName, values);
420         auto findState = values.find(power::property);
421         if (findState != values.end())
422         {
423             bool on = boost::ends_with(std::get<std::string>(findState->second),
424                                        ".Running");
425             if (!on)
426             {
427                 timer.cancel();
428                 powerStatusOn = false;
429                 hostStatusCallback(PowerState::on, powerStatusOn);
430                 return;
431             }
432             // on comes too quickly
433             timer.expires_after(std::chrono::seconds(10));
434             timer.async_wait(
435                 [hostStatusCallback](boost::system::error_code ec) {
436                 if (ec == boost::asio::error::operation_aborted)
437                 {
438                     return;
439                 }
440                 if (ec)
441                 {
442                     std::cerr << "Timer error " << ec.message() << "\n";
443                     return;
444                 }
445                 powerStatusOn = true;
446                 hostStatusCallback(PowerState::on, powerStatusOn);
447             });
448         }
449         });
450 
451     postMatch = std::make_unique<sdbusplus::bus::match_t>(
452         static_cast<sdbusplus::bus_t&>(*conn),
453         "type='signal',interface='" + std::string(properties::interface) +
454             "',path='" + std::string(post::path) + "',arg0='" +
455             std::string(post::interface) + "'",
456         [hostStatusCallback](sdbusplus::message_t& message) {
457         std::string objectName;
458         boost::container::flat_map<std::string, std::variant<std::string>>
459             values;
460         message.read(objectName, values);
461         auto findState = values.find(post::property);
462         if (findState != values.end())
463         {
464             auto& value = std::get<std::string>(findState->second);
465             biosHasPost = (value != "Inactive") &&
466                           (value != "xyz.openbmc_project.State.OperatingSystem."
467                                     "Status.OSStatus.Inactive");
468             hostStatusCallback(PowerState::biosPost, biosHasPost);
469         }
470         });
471 
472     getPowerStatus(conn);
473     getPostStatus(conn);
474 }
475 
476 void setupPowerMatch(const std::shared_ptr<sdbusplus::asio::connection>& conn)
477 {
478     setupPowerMatchCallback(conn, [](PowerState, bool) {});
479 }
480 
481 // replaces limits if MinReading and MaxReading are found.
482 void findLimits(std::pair<double, double>& limits,
483                 const SensorBaseConfiguration* data)
484 {
485     if (data == nullptr)
486     {
487         return;
488     }
489     auto maxFind = data->second.find("MaxReading");
490     auto minFind = data->second.find("MinReading");
491 
492     if (minFind != data->second.end())
493     {
494         limits.first = std::visit(VariantToDoubleVisitor(), minFind->second);
495     }
496     if (maxFind != data->second.end())
497     {
498         limits.second = std::visit(VariantToDoubleVisitor(), maxFind->second);
499     }
500 }
501 
502 void createAssociation(
503     std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
504     const std::string& path)
505 {
506     if (association)
507     {
508         fs::path p(path);
509 
510         std::vector<Association> associations;
511         associations.emplace_back("chassis", "all_sensors",
512                                   p.parent_path().string());
513         association->register_property("Associations", associations);
514         association->initialize();
515     }
516 }
517 
518 void setInventoryAssociation(
519     const std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
520     const std::string& path,
521     const std::vector<std::string>& chassisPaths = std::vector<std::string>())
522 {
523     if (association)
524     {
525         fs::path p(path);
526         std::vector<Association> associations;
527         std::string objPath(p.parent_path().string());
528 
529         associations.emplace_back("inventory", "sensors", objPath);
530         associations.emplace_back("chassis", "all_sensors", objPath);
531 
532         for (const std::string& chassisPath : chassisPaths)
533         {
534             associations.emplace_back("chassis", "all_sensors", chassisPath);
535         }
536 
537         association->register_property("Associations", associations);
538         association->initialize();
539     }
540 }
541 
542 void createInventoryAssoc(
543     const std::shared_ptr<sdbusplus::asio::connection>& conn,
544     const std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
545     const std::string& path)
546 {
547     if (!association)
548     {
549         return;
550     }
551 
552     conn->async_method_call(
553         [association, path](const boost::system::error_code ec,
554                             const std::vector<std::string>& invSysObjPaths) {
555         if (ec)
556         {
557             // In case of error, set the default associations and
558             // initialize the association Interface.
559             setInventoryAssociation(association, path);
560             return;
561         }
562         setInventoryAssociation(association, path, invSysObjPaths);
563         },
564         mapper::busName, mapper::path, mapper::interface, "GetSubTreePaths",
565         "/xyz/openbmc_project/inventory/system", 2,
566         std::array<std::string, 1>{
567             "xyz.openbmc_project.Inventory.Item.System"});
568 }
569 
570 std::optional<double> readFile(const std::string& thresholdFile,
571                                const double& scaleFactor)
572 {
573     std::string line;
574     std::ifstream labelFile(thresholdFile);
575     if (labelFile.good())
576     {
577         std::getline(labelFile, line);
578         labelFile.close();
579 
580         try
581         {
582             return std::stod(line) / scaleFactor;
583         }
584         catch (const std::invalid_argument&)
585         {
586             return std::nullopt;
587         }
588     }
589     return std::nullopt;
590 }
591 
592 std::optional<std::tuple<std::string, std::string, std::string>>
593     splitFileName(const fs::path& filePath)
594 {
595     if (filePath.has_filename())
596     {
597         const auto fileName = filePath.filename().string();
598 
599         size_t numberPos = std::strcspn(fileName.c_str(), "1234567890");
600         size_t itemPos = std::strcspn(fileName.c_str(), "_");
601 
602         if (numberPos > 0 && itemPos > numberPos && fileName.size() > itemPos)
603         {
604             return std::make_optional(
605                 std::make_tuple(fileName.substr(0, numberPos),
606                                 fileName.substr(numberPos, itemPos - numberPos),
607                                 fileName.substr(itemPos + 1, fileName.size())));
608         }
609     }
610     return std::nullopt;
611 }
612 
613 static void handleSpecialModeChange(const std::string& manufacturingModeStatus)
614 {
615     manufacturingMode = false;
616     if (manufacturingModeStatus == "xyz.openbmc_project.Control.Security."
617                                    "SpecialMode.Modes.Manufacturing")
618     {
619         manufacturingMode = true;
620     }
621     if (validateUnsecureFeature == 1)
622     {
623         if (manufacturingModeStatus == "xyz.openbmc_project.Control.Security."
624                                        "SpecialMode.Modes.ValidationUnsecure")
625         {
626             manufacturingMode = true;
627         }
628     }
629 }
630 
631 void setupManufacturingModeMatch(sdbusplus::asio::connection& conn)
632 {
633     namespace rules = sdbusplus::bus::match::rules;
634     static constexpr const char* specialModeInterface =
635         "xyz.openbmc_project.Security.SpecialMode";
636 
637     const std::string filterSpecialModeIntfAdd =
638         rules::interfacesAdded() +
639         rules::argNpath(0, "/xyz/openbmc_project/security/special_mode");
640     static std::unique_ptr<sdbusplus::bus::match_t> specialModeIntfMatch =
641         std::make_unique<sdbusplus::bus::match_t>(conn,
642                                                   filterSpecialModeIntfAdd,
643                                                   [](sdbusplus::message_t& m) {
644         sdbusplus::message::object_path path;
645         using PropertyMap =
646             boost::container::flat_map<std::string, std::variant<std::string>>;
647         boost::container::flat_map<std::string, PropertyMap> interfaceAdded;
648         m.read(path, interfaceAdded);
649         auto intfItr = interfaceAdded.find(specialModeInterface);
650         if (intfItr == interfaceAdded.end())
651         {
652             return;
653         }
654         PropertyMap& propertyList = intfItr->second;
655         auto itr = propertyList.find("SpecialMode");
656         if (itr == propertyList.end())
657         {
658             std::cerr << "error getting  SpecialMode property "
659                       << "\n";
660             return;
661         }
662         auto* manufacturingModeStatus = std::get_if<std::string>(&itr->second);
663         handleSpecialModeChange(*manufacturingModeStatus);
664         });
665 
666     const std::string filterSpecialModeChange =
667         rules::type::signal() + rules::member("PropertiesChanged") +
668         rules::interface("org.freedesktop.DBus.Properties") +
669         rules::argN(0, specialModeInterface);
670     static std::unique_ptr<sdbusplus::bus::match_t> specialModeChangeMatch =
671         std::make_unique<sdbusplus::bus::match_t>(conn, filterSpecialModeChange,
672                                                   [](sdbusplus::message_t& m) {
673         std::string interfaceName;
674         boost::container::flat_map<std::string, std::variant<std::string>>
675             propertiesChanged;
676 
677         m.read(interfaceName, propertiesChanged);
678         auto itr = propertiesChanged.find("SpecialMode");
679         if (itr == propertiesChanged.end())
680         {
681             return;
682         }
683         auto* manufacturingModeStatus = std::get_if<std::string>(&itr->second);
684         handleSpecialModeChange(*manufacturingModeStatus);
685         });
686 
687     conn.async_method_call(
688         [](const boost::system::error_code ec,
689            const std::variant<std::string>& getManufactMode) {
690         if (ec)
691         {
692             std::cerr << "error getting  SpecialMode status " << ec.message()
693                       << "\n";
694             return;
695         }
696         const auto* manufacturingModeStatus =
697             std::get_if<std::string>(&getManufactMode);
698         handleSpecialModeChange(*manufacturingModeStatus);
699         },
700         "xyz.openbmc_project.SpecialMode",
701         "/xyz/openbmc_project/security/special_mode",
702         "org.freedesktop.DBus.Properties", "Get", specialModeInterface,
703         "SpecialMode");
704 }
705 
706 bool getManufacturingMode()
707 {
708     return manufacturingMode;
709 }
710