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