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