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