xref: /openbmc/dbus-sensors/src/Utils.cpp (revision 5cf0f992)
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, const std::string& matchString,
196                std::vector<fs::path>& foundPaths, int symlinkDepth)
197 {
198     if (!fs::exists(dirPath))
199     {
200         return false;
201     }
202 
203     std::regex search(matchString);
204     std::smatch match;
205     for (auto p = fs::recursive_directory_iterator(
206              dirPath, fs::directory_options::follow_directory_symlink);
207          p != fs::recursive_directory_iterator(); ++p)
208     {
209         std::string path = p->path().string();
210         if (!is_directory(*p))
211         {
212             if (std::regex_search(path, match, search))
213             {
214                 foundPaths.emplace_back(p->path());
215             }
216         }
217         if (p.depth() >= symlinkDepth)
218         {
219             p.disable_recursion_pending();
220         }
221     }
222     return true;
223 }
224 
225 bool isPowerOn(void)
226 {
227     if (!powerMatch)
228     {
229         throw std::runtime_error("Power Match Not Created");
230     }
231     return powerStatusOn;
232 }
233 
234 bool hasBiosPost(void)
235 {
236     if (!postMatch)
237     {
238         throw std::runtime_error("Post Match Not Created");
239     }
240     return biosHasPost;
241 }
242 
243 static void
244     getPowerStatus(const std::shared_ptr<sdbusplus::asio::connection>& conn,
245                    size_t retries = 2)
246 {
247     conn->async_method_call(
248         [conn, retries](boost::system::error_code ec,
249                         const std::variant<std::string>& state) {
250             if (ec)
251             {
252                 if (retries)
253                 {
254                     auto timer = std::make_shared<boost::asio::steady_timer>(
255                         conn->get_io_context());
256                     timer->expires_after(std::chrono::seconds(15));
257                     timer->async_wait(
258                         [timer, conn, retries](boost::system::error_code) {
259                             getPowerStatus(conn, retries - 1);
260                         });
261                     return;
262                 }
263 
264                 // we commonly come up before power control, we'll capture the
265                 // property change later
266                 std::cerr << "error getting power status " << ec.message()
267                           << "\n";
268                 return;
269             }
270             powerStatusOn =
271                 boost::ends_with(std::get<std::string>(state), ".Running");
272         },
273         power::busname, power::path, properties::interface, properties::get,
274         power::interface, power::property);
275 }
276 
277 static void
278     getPostStatus(const std::shared_ptr<sdbusplus::asio::connection>& conn,
279                   size_t retries = 2)
280 {
281     conn->async_method_call(
282         [conn, retries](boost::system::error_code ec,
283                         const std::variant<std::string>& state) {
284             if (ec)
285             {
286                 if (retries)
287                 {
288                     auto timer = std::make_shared<boost::asio::steady_timer>(
289                         conn->get_io_context());
290                     timer->expires_after(std::chrono::seconds(15));
291                     timer->async_wait(
292                         [timer, conn, retries](boost::system::error_code) {
293                             getPostStatus(conn, retries - 1);
294                         });
295                     return;
296                 }
297                 // we commonly come up before power control, we'll capture the
298                 // property change later
299                 std::cerr << "error getting post status " << ec.message()
300                           << "\n";
301                 return;
302             }
303             biosHasPost = std::get<std::string>(state) != "Inactive";
304         },
305         post::busname, post::path, properties::interface, properties::get,
306         post::interface, post::property);
307 }
308 
309 void setupPowerMatch(const std::shared_ptr<sdbusplus::asio::connection>& conn)
310 {
311     static boost::asio::steady_timer timer(conn->get_io_context());
312     // create a match for powergood changes, first time do a method call to
313     // cache the correct value
314     if (powerMatch)
315     {
316         return;
317     }
318 
319     powerMatch = std::make_unique<sdbusplus::bus::match::match>(
320         static_cast<sdbusplus::bus::bus&>(*conn),
321         "type='signal',interface='" + std::string(properties::interface) +
322             "',path='" + std::string(power::path) + "',arg0='" +
323             std::string(power::interface) + "'",
324         [](sdbusplus::message::message& message) {
325             std::string objectName;
326             boost::container::flat_map<std::string, std::variant<std::string>>
327                 values;
328             message.read(objectName, values);
329             auto findState = values.find(power::property);
330             if (findState != values.end())
331             {
332                 bool on = boost::ends_with(
333                     std::get<std::string>(findState->second), ".Running");
334                 if (!on)
335                 {
336                     timer.cancel();
337                     powerStatusOn = false;
338                     return;
339                 }
340                 // on comes too quickly
341                 timer.expires_after(std::chrono::seconds(10));
342                 timer.async_wait([](boost::system::error_code ec) {
343                     if (ec == boost::asio::error::operation_aborted)
344                     {
345                         return;
346                     }
347                     if (ec)
348                     {
349                         std::cerr << "Timer error " << ec.message() << "\n";
350                         return;
351                     }
352                     powerStatusOn = true;
353                 });
354             }
355         });
356 
357     postMatch = std::make_unique<sdbusplus::bus::match::match>(
358         static_cast<sdbusplus::bus::bus&>(*conn),
359         "type='signal',interface='" + std::string(properties::interface) +
360             "',path='" + std::string(post::path) + "',arg0='" +
361             std::string(post::interface) + "'",
362         [](sdbusplus::message::message& message) {
363             std::string objectName;
364             boost::container::flat_map<std::string, std::variant<std::string>>
365                 values;
366             message.read(objectName, values);
367             auto findState = values.find(post::property);
368             if (findState != values.end())
369             {
370                 biosHasPost =
371                     std::get<std::string>(findState->second) != "Inactive";
372             }
373         });
374 
375     getPowerStatus(conn);
376     getPostStatus(conn);
377 }
378 
379 // replaces limits if MinReading and MaxReading are found.
380 void findLimits(std::pair<double, double>& limits,
381                 const SensorBaseConfiguration* data)
382 {
383     if (!data)
384     {
385         return;
386     }
387     auto maxFind = data->second.find("MaxReading");
388     auto minFind = data->second.find("MinReading");
389 
390     if (minFind != data->second.end())
391     {
392         limits.first = std::visit(VariantToDoubleVisitor(), minFind->second);
393     }
394     if (maxFind != data->second.end())
395     {
396         limits.second = std::visit(VariantToDoubleVisitor(), maxFind->second);
397     }
398 }
399 
400 void createAssociation(
401     std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
402     const std::string& path)
403 {
404     if (association)
405     {
406         std::filesystem::path p(path);
407 
408         std::vector<Association> associations;
409         associations.emplace_back("chassis", "all_sensors",
410                                   p.parent_path().string());
411         association->register_property("Associations", associations);
412         association->initialize();
413     }
414 }
415 
416 void setInventoryAssociation(
417     const std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
418     const std::string& path,
419     const std::vector<std::string>& chassisPaths = std::vector<std::string>())
420 {
421     if (association)
422     {
423         std::filesystem::path p(path);
424         std::vector<Association> associations;
425         std::string objPath(p.parent_path().string());
426 
427         associations.emplace_back("inventory", "sensors", objPath);
428         associations.emplace_back("chassis", "all_sensors", objPath);
429 
430         for (const std::string& chassisPath : chassisPaths)
431         {
432             associations.emplace_back("chassis", "all_sensors", chassisPath);
433         }
434 
435         association->register_property("Associations", associations);
436         association->initialize();
437     }
438 }
439 
440 void createInventoryAssoc(
441     const std::shared_ptr<sdbusplus::asio::connection>& conn,
442     const std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
443     const std::string& path)
444 {
445     if (!association)
446     {
447         return;
448     }
449 
450     conn->async_method_call(
451         [association, path](const boost::system::error_code ec,
452                             const std::vector<std::string>& invSysObjPaths) {
453             if (ec)
454             {
455                 // In case of error, set the default associations and
456                 // initialize the association Interface.
457                 setInventoryAssociation(association, path);
458                 return;
459             }
460             setInventoryAssociation(association, path, invSysObjPaths);
461         },
462         mapper::busName, mapper::path, mapper::interface, "GetSubTreePaths",
463         "/xyz/openbmc_project/inventory/system", 2,
464         std::array<std::string, 1>{
465             "xyz.openbmc_project.Inventory.Item.System"});
466 }
467 
468 std::optional<double> readFile(const std::string& thresholdFile,
469                                const double& scaleFactor)
470 {
471     std::string line;
472     std::ifstream labelFile(thresholdFile);
473     if (labelFile.good())
474     {
475         std::getline(labelFile, line);
476         labelFile.close();
477 
478         try
479         {
480             return std::stod(line) / scaleFactor;
481         }
482         catch (const std::invalid_argument&)
483         {
484             return std::nullopt;
485         }
486     }
487     return std::nullopt;
488 }
489 
490 std::optional<std::tuple<std::string, std::string, std::string>>
491     splitFileName(const std::filesystem::path& filePath)
492 {
493     if (filePath.has_filename())
494     {
495         const auto fileName = filePath.filename().string();
496 
497         size_t numberPos = std::strcspn(fileName.c_str(), "1234567890");
498         size_t itemPos = std::strcspn(fileName.c_str(), "_");
499 
500         if (numberPos > 0 && itemPos > numberPos && fileName.size() > itemPos)
501         {
502             return std::make_optional(
503                 std::make_tuple(fileName.substr(0, numberPos),
504                                 fileName.substr(numberPos, itemPos - numberPos),
505                                 fileName.substr(itemPos + 1, fileName.size())));
506         }
507     }
508     return std::nullopt;
509 }
510 
511 void setupManufacturingModeMatch(sdbusplus::asio::connection& conn)
512 {
513     static std::unique_ptr<sdbusplus::bus::match::match>
514         setupManufacturingModeMatch =
515             std::make_unique<sdbusplus::bus::match::match>(
516                 conn,
517                 "type='signal',interface='org.freedesktop.DBus."
518                 "Properties',member='"
519                 "PropertiesChanged',arg0namespace='xyz.openbmc_project."
520                 "Security.SpecialMode'",
521                 [](sdbusplus::message::message& msg) {
522                     std::string interfaceName;
523                     boost::container::flat_map<std::string,
524                                                std::variant<std::string>>
525                         propertiesChanged;
526                     std::string manufacturingModeStatus;
527 
528                     msg.read(interfaceName, propertiesChanged);
529                     if (propertiesChanged.begin() == propertiesChanged.end())
530                     {
531                         return;
532                     }
533 
534                     manufacturingModeStatus = std::get<std::string>(
535                         propertiesChanged.begin()->second);
536                     manufacturingMode = false;
537                     if (manufacturingModeStatus ==
538                         "xyz.openbmc_project.Control.Security."
539                         "SpecialMode.Modes.Manufacturing")
540                     {
541                         manufacturingMode = true;
542                     }
543                     if (validateUnsecureFeature == true)
544                     {
545                         if (manufacturingModeStatus ==
546                             "xyz.openbmc_project.Control.Security."
547                             "SpecialMode.Modes.ValidationUnsecure")
548                         {
549                             manufacturingMode = true;
550                         }
551                     }
552                 });
553 
554     return;
555 }
556 
557 bool getManufacturingMode()
558 {
559     return manufacturingMode;
560 }