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