xref: /openbmc/dbus-sensors/src/Utils.cpp (revision 2a40e939)
1 /*
2 // Copyright (c) 2017 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 
17 #include "Utils.hpp"
18 
19 #include <boost/algorithm/string/predicate.hpp>
20 #include <boost/container/flat_map.hpp>
21 #include <sdbusplus/asio/connection.hpp>
22 #include <sdbusplus/asio/object_server.hpp>
23 #include <sdbusplus/bus/match.hpp>
24 
25 #include <filesystem>
26 #include <fstream>
27 #include <memory>
28 #include <regex>
29 #include <stdexcept>
30 #include <string>
31 #include <utility>
32 #include <variant>
33 #include <vector>
34 
35 namespace fs = std::filesystem;
36 
37 static bool powerStatusOn = false;
38 static bool biosHasPost = false;
39 
40 static std::unique_ptr<sdbusplus::bus::match::match> powerMatch = nullptr;
41 static std::unique_ptr<sdbusplus::bus::match::match> postMatch = nullptr;
42 
43 /**
44  * return the contents of a file
45  * @param[in] hwmonFile - the path to the file to read
46  * @return the contents of the file as a string or nullopt if the file could not
47  * be opened.
48  */
49 
50 std::optional<std::string> openAndRead(const std::string& hwmonFile)
51 {
52     std::string fileVal;
53     std::ifstream fileStream(hwmonFile);
54     if (!fileStream.is_open())
55     {
56         return std::nullopt;
57     }
58     std::getline(fileStream, fileVal);
59     return fileVal;
60 }
61 
62 /**
63  * given a hwmon temperature base name if valid return the full path else
64  * nullopt
65  * @param[in] directory - the hwmon sysfs directory
66  * @param[in] permitSet - a set of labels or hwmon basenames to permit. If this
67  * is empty then *everything* is permitted.
68  * @return a string to the full path of the file to create a temp sensor with or
69  * nullopt to indicate that no sensor should be created for this basename.
70  */
71 std::optional<std::string>
72     getFullHwmonFilePath(const std::string& directory,
73                          const std::string& hwmonBaseName,
74                          const std::set<std::string>& permitSet)
75 {
76     std::optional<std::string> result;
77     std::string filename;
78     if (permitSet.empty())
79     {
80         result = directory + "/" + hwmonBaseName + "_input";
81         return result;
82     }
83     filename = directory + "/" + hwmonBaseName + "_label";
84     auto searchVal = openAndRead(filename);
85     if (!searchVal)
86     {
87         /* if the hwmon temp doesn't have a corresponding label file
88          * then use the hwmon temperature base name
89          */
90         searchVal = hwmonBaseName;
91     }
92     if (permitSet.find(*searchVal) != permitSet.end())
93     {
94         result = directory + "/" + hwmonBaseName + "_input";
95     }
96     return result;
97 }
98 
99 /**
100  * retrieve a set of basenames and labels to allow sensor creation for.
101  * @param[in] config - a map representing the configuration for a specific
102  * device
103  * @return a set of basenames and labels to allow sensor creation for. An empty
104  * set indicates that everything is permitted.
105  */
106 std::set<std::string> getPermitSet(const SensorBaseConfigMap& config)
107 {
108     auto permitAttribute = config.find("Labels");
109     std::set<std::string> permitSet;
110     if (permitAttribute != config.end())
111     {
112         try
113         {
114             auto val =
115                 std::get<std::vector<std::string>>(permitAttribute->second);
116 
117             permitSet.insert(std::make_move_iterator(val.begin()),
118                              std::make_move_iterator(val.end()));
119         }
120         catch (const std::bad_variant_access& err)
121         {
122             std::cerr << err.what()
123                       << ":PermitList does not contain a list, wrong "
124                          "variant type.\n";
125         }
126     }
127     return permitSet;
128 }
129 
130 bool getSensorConfiguration(
131     const std::string& type,
132     const std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
133     ManagedObjectType& resp, bool useCache)
134 {
135     static ManagedObjectType managedObj;
136 
137     if (!useCache)
138     {
139         managedObj.clear();
140         sdbusplus::message::message getManagedObjects =
141             dbusConnection->new_method_call(
142                 entityManagerName, "/", "org.freedesktop.DBus.ObjectManager",
143                 "GetManagedObjects");
144         bool err = false;
145         try
146         {
147             sdbusplus::message::message reply =
148                 dbusConnection->call(getManagedObjects);
149             reply.read(managedObj);
150         }
151         catch (const sdbusplus::exception::exception& e)
152         {
153             std::cerr << "While calling GetManagedObjects on service:"
154                       << entityManagerName << " exception name:" << e.name()
155                       << "and description:" << e.description()
156                       << " was thrown\n";
157             err = true;
158         }
159 
160         if (err)
161         {
162             std::cerr << "Error communicating to entity manager\n";
163             return false;
164         }
165     }
166     for (const auto& pathPair : managedObj)
167     {
168         bool correctType = false;
169         for (const auto& entry : pathPair.second)
170         {
171             if (boost::starts_with(entry.first, type))
172             {
173                 correctType = true;
174                 break;
175             }
176         }
177         if (correctType)
178         {
179             resp.emplace(pathPair);
180         }
181     }
182     return true;
183 }
184 
185 bool findFiles(const fs::path dirPath, const std::string& matchString,
186                std::vector<fs::path>& foundPaths, unsigned int symlinkDepth)
187 {
188     if (!fs::exists(dirPath))
189         return false;
190 
191     std::regex search(matchString);
192     std::smatch match;
193     for (auto& p : fs::recursive_directory_iterator(dirPath))
194     {
195         std::string path = p.path().string();
196         if (!is_directory(p))
197         {
198             if (std::regex_search(path, match, search))
199                 foundPaths.emplace_back(p.path());
200         }
201         else if (is_symlink(p) && symlinkDepth)
202         {
203             findFiles(p.path(), matchString, foundPaths, symlinkDepth - 1);
204         }
205     }
206     return true;
207 }
208 
209 bool isPowerOn(void)
210 {
211     if (!powerMatch)
212     {
213         throw std::runtime_error("Power Match Not Created");
214     }
215     return powerStatusOn;
216 }
217 
218 bool hasBiosPost(void)
219 {
220     if (!postMatch)
221     {
222         throw std::runtime_error("Post Match Not Created");
223     }
224     return biosHasPost;
225 }
226 
227 static void
228     getPowerStatus(const std::shared_ptr<sdbusplus::asio::connection>& conn,
229                    size_t retries = 2)
230 {
231     conn->async_method_call(
232         [conn, retries](boost::system::error_code ec,
233                         const std::variant<std::string>& state) {
234             if (ec)
235             {
236                 if (retries)
237                 {
238                     auto timer = std::make_shared<boost::asio::steady_timer>(
239                         conn->get_io_context());
240                     timer->expires_after(std::chrono::seconds(15));
241                     timer->async_wait(
242                         [timer, conn, retries](boost::system::error_code) {
243                             getPowerStatus(conn, retries - 1);
244                         });
245                     return;
246                 }
247 
248                 // we commonly come up before power control, we'll capture the
249                 // property change later
250                 std::cerr << "error getting power status " << ec.message()
251                           << "\n";
252                 return;
253             }
254             powerStatusOn =
255                 boost::ends_with(std::get<std::string>(state), "Running");
256         },
257         power::busname, power::path, properties::interface, properties::get,
258         power::interface, power::property);
259 }
260 
261 static void
262     getPostStatus(const std::shared_ptr<sdbusplus::asio::connection>& conn,
263                   size_t retries = 2)
264 {
265     conn->async_method_call(
266         [conn, retries](boost::system::error_code ec,
267                         const std::variant<std::string>& state) {
268             if (ec)
269             {
270                 if (retries)
271                 {
272                     auto timer = std::make_shared<boost::asio::steady_timer>(
273                         conn->get_io_context());
274                     timer->expires_after(std::chrono::seconds(15));
275                     timer->async_wait(
276                         [timer, conn, retries](boost::system::error_code) {
277                             getPostStatus(conn, retries - 1);
278                         });
279                     return;
280                 }
281                 // we commonly come up before power control, we'll capture the
282                 // property change later
283                 std::cerr << "error getting post status " << ec.message()
284                           << "\n";
285                 return;
286             }
287             biosHasPost = std::get<std::string>(state) != "Inactive";
288         },
289         post::busname, post::path, properties::interface, properties::get,
290         post::interface, post::property);
291 }
292 
293 void setupPowerMatch(const std::shared_ptr<sdbusplus::asio::connection>& conn)
294 {
295     static boost::asio::steady_timer timer(conn->get_io_context());
296     // create a match for powergood changes, first time do a method call to
297     // cache the correct value
298     if (powerMatch)
299     {
300         return;
301     }
302 
303     powerMatch = std::make_unique<sdbusplus::bus::match::match>(
304         static_cast<sdbusplus::bus::bus&>(*conn),
305         "type='signal',interface='" + std::string(properties::interface) +
306             "',path='" + std::string(power::path) + "',arg0='" +
307             std::string(power::interface) + "'",
308         [](sdbusplus::message::message& message) {
309             std::string objectName;
310             boost::container::flat_map<std::string, std::variant<std::string>>
311                 values;
312             message.read(objectName, values);
313             auto findState = values.find(power::property);
314             if (findState != values.end())
315             {
316                 bool on = boost::ends_with(
317                     std::get<std::string>(findState->second), "Running");
318                 if (!on)
319                 {
320                     timer.cancel();
321                     powerStatusOn = false;
322                     return;
323                 }
324                 // on comes too quickly
325                 timer.expires_after(std::chrono::seconds(10));
326                 timer.async_wait([](boost::system::error_code ec) {
327                     if (ec == boost::asio::error::operation_aborted)
328                     {
329                         return;
330                     }
331                     else if (ec)
332                     {
333                         std::cerr << "Timer error " << ec.message() << "\n";
334                         return;
335                     }
336                     powerStatusOn = true;
337                 });
338             }
339         });
340 
341     postMatch = std::make_unique<sdbusplus::bus::match::match>(
342         static_cast<sdbusplus::bus::bus&>(*conn),
343         "type='signal',interface='" + std::string(properties::interface) +
344             "',path='" + std::string(post::path) + "',arg0='" +
345             std::string(post::interface) + "'",
346         [](sdbusplus::message::message& message) {
347             std::string objectName;
348             boost::container::flat_map<std::string, std::variant<std::string>>
349                 values;
350             message.read(objectName, values);
351             auto findState = values.find(post::property);
352             if (findState != values.end())
353             {
354                 biosHasPost =
355                     std::get<std::string>(findState->second) != "Inactive";
356             }
357         });
358 
359     getPowerStatus(conn);
360     getPostStatus(conn);
361 }
362 
363 // replaces limits if MinReading and MaxReading are found.
364 void findLimits(std::pair<double, double>& limits,
365                 const SensorBaseConfiguration* data)
366 {
367     if (!data)
368     {
369         return;
370     }
371     auto maxFind = data->second.find("MaxReading");
372     auto minFind = data->second.find("MinReading");
373 
374     if (minFind != data->second.end())
375     {
376         limits.first = std::visit(VariantToDoubleVisitor(), minFind->second);
377     }
378     if (maxFind != data->second.end())
379     {
380         limits.second = std::visit(VariantToDoubleVisitor(), maxFind->second);
381     }
382 }
383 
384 void createAssociation(
385     std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
386     const std::string& path)
387 {
388     if (association)
389     {
390         std::filesystem::path p(path);
391 
392         std::vector<Association> associations;
393         associations.push_back(
394             Association("chassis", "all_sensors", p.parent_path().string()));
395         association->register_property("Associations", associations);
396         association->initialize();
397     }
398 }
399 
400 void setInventoryAssociation(
401     std::shared_ptr<sdbusplus::asio::dbus_interface> association,
402     const std::string& path,
403     const std::vector<std::string>& chassisPaths = std::vector<std::string>())
404 {
405     if (association)
406     {
407         std::filesystem::path p(path);
408         std::vector<Association> associations;
409         std::string objPath(p.parent_path().string());
410 
411         associations.push_back(Association("inventory", "sensors", objPath));
412         associations.push_back(Association("chassis", "all_sensors", objPath));
413 
414         for (const std::string& chassisPath : chassisPaths)
415         {
416             associations.push_back(
417                 Association("chassis", "all_sensors", chassisPath));
418         }
419 
420         association->register_property("Associations", associations);
421         association->initialize();
422     }
423 }
424 
425 void createInventoryAssoc(
426     std::shared_ptr<sdbusplus::asio::connection> conn,
427     std::shared_ptr<sdbusplus::asio::dbus_interface> association,
428     const std::string& path)
429 {
430     if (!association)
431     {
432         return;
433     }
434 
435     conn->async_method_call(
436         [association, path](const boost::system::error_code ec,
437                             const std::vector<std::string>& invSysObjPaths) {
438             if (ec)
439             {
440                 // In case of error, set the default associations and
441                 // initialize the association Interface.
442                 setInventoryAssociation(association, path);
443                 return;
444             }
445             setInventoryAssociation(association, path, invSysObjPaths);
446         },
447         mapper::busName, mapper::path, mapper::interface, "GetSubTreePaths",
448         "/xyz/openbmc_project/inventory/system", 2,
449         std::array<std::string, 1>{
450             "xyz.openbmc_project.Inventory.Item.System"});
451 }
452 
453 std::optional<double> readFile(const std::string& thresholdFile,
454                                const double& scaleFactor)
455 {
456     std::string line;
457     std::ifstream labelFile(thresholdFile);
458     if (labelFile.good())
459     {
460         std::getline(labelFile, line);
461         labelFile.close();
462 
463         try
464         {
465             return std::stod(line) / scaleFactor;
466         }
467         catch (const std::invalid_argument&)
468         {
469             return std::nullopt;
470         }
471     }
472     return std::nullopt;
473 }
474 
475 std::optional<std::tuple<std::string, std::string, std::string>>
476     splitFileName(const std::filesystem::path& filePath)
477 {
478     if (filePath.has_filename())
479     {
480         const auto fileName = filePath.filename().string();
481 
482         size_t numberPos = std::strcspn(fileName.c_str(), "1234567890");
483         size_t itemPos = std::strcspn(fileName.c_str(), "_");
484 
485         if (numberPos > 0 && itemPos > numberPos && fileName.size() > itemPos)
486         {
487             return std::make_optional(
488                 std::make_tuple(fileName.substr(0, numberPos),
489                                 fileName.substr(numberPos, itemPos - numberPos),
490                                 fileName.substr(itemPos + 1, fileName.size())));
491         }
492     }
493     return std::nullopt;
494 }
495