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/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 static bool manufacturingMode = false; 40 41 static std::unique_ptr<sdbusplus::bus::match_t> powerMatch = nullptr; 42 static std::unique_ptr<sdbusplus::bus::match_t> postMatch = nullptr; 43 44 /** 45 * return the contents of a file 46 * @param[in] hwmonFile - the path to the file to read 47 * @return the contents of the file as a string or nullopt if the file could not 48 * be opened. 49 */ 50 51 std::optional<std::string> openAndRead(const std::string& hwmonFile) 52 { 53 std::string fileVal; 54 std::ifstream fileStream(hwmonFile); 55 if (!fileStream.is_open()) 56 { 57 return std::nullopt; 58 } 59 std::getline(fileStream, fileVal); 60 return fileVal; 61 } 62 63 /** 64 * given a hwmon temperature base name if valid return the full path else 65 * nullopt 66 * @param[in] directory - the hwmon sysfs directory 67 * @param[in] permitSet - a set of labels or hwmon basenames to permit. If this 68 * is empty then *everything* is permitted. 69 * @return a string to the full path of the file to create a temp sensor with or 70 * nullopt to indicate that no sensor should be created for this basename. 71 */ 72 std::optional<std::string> 73 getFullHwmonFilePath(const std::string& directory, 74 const std::string& hwmonBaseName, 75 const std::set<std::string>& permitSet) 76 { 77 std::optional<std::string> result; 78 std::string filename; 79 if (permitSet.empty()) 80 { 81 result = directory + "/" + hwmonBaseName + "_input"; 82 return result; 83 } 84 filename = directory + "/" + hwmonBaseName + "_label"; 85 auto searchVal = openAndRead(filename); 86 if (!searchVal) 87 { 88 /* if the hwmon temp doesn't have a corresponding label file 89 * then use the hwmon temperature base name 90 */ 91 searchVal = hwmonBaseName; 92 } 93 if (permitSet.find(*searchVal) != permitSet.end()) 94 { 95 result = directory + "/" + hwmonBaseName + "_input"; 96 } 97 return result; 98 } 99 100 /** 101 * retrieve a set of basenames and labels to allow sensor creation for. 102 * @param[in] config - a map representing the configuration for a specific 103 * device 104 * @return a set of basenames and labels to allow sensor creation for. An empty 105 * set indicates that everything is permitted. 106 */ 107 std::set<std::string> getPermitSet(const SensorBaseConfigMap& config) 108 { 109 auto permitAttribute = config.find("Labels"); 110 std::set<std::string> permitSet; 111 if (permitAttribute != config.end()) 112 { 113 try 114 { 115 auto val = 116 std::get<std::vector<std::string>>(permitAttribute->second); 117 118 permitSet.insert(std::make_move_iterator(val.begin()), 119 std::make_move_iterator(val.end())); 120 } 121 catch (const std::bad_variant_access& err) 122 { 123 std::cerr << err.what() 124 << ":PermitList does not contain a list, wrong " 125 "variant type.\n"; 126 } 127 } 128 return permitSet; 129 } 130 131 bool getSensorConfiguration( 132 const std::string& type, 133 const std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, 134 ManagedObjectType& resp) 135 { 136 return getSensorConfiguration(type, dbusConnection, resp, false); 137 } 138 139 bool getSensorConfiguration( 140 const std::string& type, 141 const std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, 142 ManagedObjectType& resp, bool useCache) 143 { 144 static ManagedObjectType managedObj; 145 146 if (!useCache) 147 { 148 managedObj.clear(); 149 sdbusplus::message_t getManagedObjects = 150 dbusConnection->new_method_call( 151 entityManagerName, "/", "org.freedesktop.DBus.ObjectManager", 152 "GetManagedObjects"); 153 bool err = false; 154 try 155 { 156 sdbusplus::message_t reply = 157 dbusConnection->call(getManagedObjects); 158 reply.read(managedObj); 159 } 160 catch (const sdbusplus::exception_t& e) 161 { 162 std::cerr << "While calling GetManagedObjects on service:" 163 << entityManagerName << " exception name:" << e.name() 164 << "and description:" << e.description() 165 << " was thrown\n"; 166 err = true; 167 } 168 169 if (err) 170 { 171 std::cerr << "Error communicating to entity manager\n"; 172 return false; 173 } 174 } 175 for (const auto& pathPair : managedObj) 176 { 177 bool correctType = false; 178 for (const auto& [intf, cfg] : pathPair.second) 179 { 180 if (intf.starts_with(type)) 181 { 182 correctType = true; 183 break; 184 } 185 } 186 if (correctType) 187 { 188 resp.emplace(pathPair); 189 } 190 } 191 return true; 192 } 193 194 bool findFiles(const fs::path& dirPath, std::string_view matchString, 195 std::vector<fs::path>& foundPaths, int symlinkDepth) 196 { 197 std::error_code ec; 198 if (!fs::exists(dirPath, ec)) 199 { 200 return false; 201 } 202 203 std::vector<std::regex> matchPieces; 204 205 size_t pos = 0; 206 std::string token; 207 // Generate the regex expressions list from the match we were given 208 while ((pos = matchString.find('/')) != std::string::npos) 209 { 210 token = matchString.substr(0, pos); 211 matchPieces.emplace_back(token); 212 matchString.remove_prefix(pos + 1); 213 } 214 matchPieces.emplace_back(std::string{matchString}); 215 216 // Check if the match string contains directories, and skip the match of 217 // subdirectory if not 218 if (matchPieces.size() <= 1) 219 { 220 std::regex search(std::string{matchString}); 221 std::smatch match; 222 for (auto p = fs::recursive_directory_iterator( 223 dirPath, fs::directory_options::follow_directory_symlink); 224 p != fs::recursive_directory_iterator(); ++p) 225 { 226 std::string path = p->path().string(); 227 if (!is_directory(*p)) 228 { 229 if (std::regex_search(path, match, search)) 230 { 231 foundPaths.emplace_back(p->path()); 232 } 233 } 234 if (p.depth() >= symlinkDepth) 235 { 236 p.disable_recursion_pending(); 237 } 238 } 239 return true; 240 } 241 242 // The match string contains directories, verify each level of sub 243 // directories 244 for (auto p = fs::recursive_directory_iterator( 245 dirPath, fs::directory_options::follow_directory_symlink); 246 p != fs::recursive_directory_iterator(); ++p) 247 { 248 std::vector<std::regex>::iterator matchPiece = matchPieces.begin(); 249 fs::path::iterator pathIt = p->path().begin(); 250 for (const fs::path& dir : dirPath) 251 { 252 if (dir.empty()) 253 { 254 // When the path ends with '/', it gets am empty path 255 // skip such case. 256 break; 257 } 258 pathIt++; 259 } 260 261 while (pathIt != p->path().end()) 262 { 263 // Found a path deeper than match. 264 if (matchPiece == matchPieces.end()) 265 { 266 p.disable_recursion_pending(); 267 break; 268 } 269 std::smatch match; 270 std::string component = pathIt->string(); 271 std::regex regexPiece(*matchPiece); 272 if (!std::regex_match(component, match, regexPiece)) 273 { 274 // path prefix doesn't match, no need to iterate further 275 p.disable_recursion_pending(); 276 break; 277 } 278 matchPiece++; 279 pathIt++; 280 } 281 282 if (!is_directory(*p)) 283 { 284 if (matchPiece == matchPieces.end()) 285 { 286 foundPaths.emplace_back(p->path()); 287 } 288 } 289 290 if (p.depth() >= symlinkDepth) 291 { 292 p.disable_recursion_pending(); 293 } 294 } 295 return true; 296 } 297 298 bool isPowerOn(void) 299 { 300 if (!powerMatch) 301 { 302 throw std::runtime_error("Power Match Not Created"); 303 } 304 return powerStatusOn; 305 } 306 307 bool hasBiosPost(void) 308 { 309 if (!postMatch) 310 { 311 throw std::runtime_error("Post Match Not Created"); 312 } 313 return biosHasPost; 314 } 315 316 bool readingStateGood(const PowerState& powerState) 317 { 318 if (powerState == PowerState::on && !isPowerOn()) 319 { 320 return false; 321 } 322 if (powerState == PowerState::biosPost && (!hasBiosPost() || !isPowerOn())) 323 { 324 return false; 325 } 326 327 return true; 328 } 329 330 static void 331 getPowerStatus(const std::shared_ptr<sdbusplus::asio::connection>& conn, 332 size_t retries = 2) 333 { 334 conn->async_method_call( 335 [conn, retries](boost::system::error_code ec, 336 const std::variant<std::string>& state) { 337 if (ec) 338 { 339 if (retries != 0U) 340 { 341 auto timer = std::make_shared<boost::asio::steady_timer>( 342 conn->get_io_context()); 343 timer->expires_after(std::chrono::seconds(15)); 344 timer->async_wait( 345 [timer, conn, retries](boost::system::error_code) { 346 getPowerStatus(conn, retries - 1); 347 }); 348 return; 349 } 350 351 // we commonly come up before power control, we'll capture the 352 // property change later 353 std::cerr << "error getting power status " << ec.message() << "\n"; 354 return; 355 } 356 powerStatusOn = std::get<std::string>(state).ends_with(".Running"); 357 }, 358 power::busname, power::path, properties::interface, properties::get, 359 power::interface, power::property); 360 } 361 362 static void 363 getPostStatus(const std::shared_ptr<sdbusplus::asio::connection>& conn, 364 size_t retries = 2) 365 { 366 conn->async_method_call( 367 [conn, retries](boost::system::error_code ec, 368 const std::variant<std::string>& state) { 369 if (ec) 370 { 371 if (retries != 0U) 372 { 373 auto timer = std::make_shared<boost::asio::steady_timer>( 374 conn->get_io_context()); 375 timer->expires_after(std::chrono::seconds(15)); 376 timer->async_wait( 377 [timer, conn, retries](boost::system::error_code) { 378 getPostStatus(conn, retries - 1); 379 }); 380 return; 381 } 382 // we commonly come up before power control, we'll capture the 383 // property change later 384 std::cerr << "error getting post status " << ec.message() << "\n"; 385 return; 386 } 387 const auto& value = std::get<std::string>(state); 388 biosHasPost = (value != "Inactive") && 389 (value != "xyz.openbmc_project.State.OperatingSystem." 390 "Status.OSStatus.Inactive"); 391 }, 392 post::busname, post::path, properties::interface, properties::get, 393 post::interface, post::property); 394 } 395 396 void setupPowerMatchCallback( 397 const std::shared_ptr<sdbusplus::asio::connection>& conn, 398 std::function<void(PowerState type, bool state)>&& hostStatusCallback) 399 { 400 static boost::asio::steady_timer timer(conn->get_io_context()); 401 // create a match for powergood changes, first time do a method call to 402 // cache the correct value 403 if (powerMatch) 404 { 405 return; 406 } 407 408 powerMatch = std::make_unique<sdbusplus::bus::match_t>( 409 static_cast<sdbusplus::bus_t&>(*conn), 410 "type='signal',interface='" + std::string(properties::interface) + 411 "',path='" + std::string(power::path) + "',arg0='" + 412 std::string(power::interface) + "'", 413 [hostStatusCallback](sdbusplus::message_t& message) { 414 std::string objectName; 415 boost::container::flat_map<std::string, std::variant<std::string>> 416 values; 417 message.read(objectName, values); 418 auto findState = values.find(power::property); 419 if (findState != values.end()) 420 { 421 bool on = 422 std::get<std::string>(findState->second).ends_with(".Running"); 423 if (!on) 424 { 425 timer.cancel(); 426 powerStatusOn = false; 427 hostStatusCallback(PowerState::on, powerStatusOn); 428 return; 429 } 430 // on comes too quickly 431 timer.expires_after(std::chrono::seconds(10)); 432 timer.async_wait( 433 [hostStatusCallback](boost::system::error_code ec) { 434 if (ec == boost::asio::error::operation_aborted) 435 { 436 return; 437 } 438 if (ec) 439 { 440 std::cerr << "Timer error " << ec.message() << "\n"; 441 return; 442 } 443 powerStatusOn = true; 444 hostStatusCallback(PowerState::on, powerStatusOn); 445 }); 446 } 447 }); 448 449 postMatch = std::make_unique<sdbusplus::bus::match_t>( 450 static_cast<sdbusplus::bus_t&>(*conn), 451 "type='signal',interface='" + std::string(properties::interface) + 452 "',path='" + std::string(post::path) + "',arg0='" + 453 std::string(post::interface) + "'", 454 [hostStatusCallback](sdbusplus::message_t& message) { 455 std::string objectName; 456 boost::container::flat_map<std::string, std::variant<std::string>> 457 values; 458 message.read(objectName, values); 459 auto findState = values.find(post::property); 460 if (findState != values.end()) 461 { 462 auto& value = std::get<std::string>(findState->second); 463 biosHasPost = (value != "Inactive") && 464 (value != "xyz.openbmc_project.State.OperatingSystem." 465 "Status.OSStatus.Inactive"); 466 hostStatusCallback(PowerState::biosPost, biosHasPost); 467 } 468 }); 469 470 getPowerStatus(conn); 471 getPostStatus(conn); 472 } 473 474 void setupPowerMatch(const std::shared_ptr<sdbusplus::asio::connection>& conn) 475 { 476 setupPowerMatchCallback(conn, [](PowerState, bool) {}); 477 } 478 479 // replaces limits if MinReading and MaxReading are found. 480 void findLimits(std::pair<double, double>& limits, 481 const SensorBaseConfiguration* data) 482 { 483 if (data == nullptr) 484 { 485 return; 486 } 487 auto maxFind = data->second.find("MaxReading"); 488 auto minFind = data->second.find("MinReading"); 489 490 if (minFind != data->second.end()) 491 { 492 limits.first = std::visit(VariantToDoubleVisitor(), minFind->second); 493 } 494 if (maxFind != data->second.end()) 495 { 496 limits.second = std::visit(VariantToDoubleVisitor(), maxFind->second); 497 } 498 } 499 500 void createAssociation( 501 std::shared_ptr<sdbusplus::asio::dbus_interface>& association, 502 const std::string& path) 503 { 504 if (association) 505 { 506 fs::path p(path); 507 508 std::vector<Association> associations; 509 associations.emplace_back("chassis", "all_sensors", 510 p.parent_path().string()); 511 association->register_property("Associations", associations); 512 association->initialize(); 513 } 514 } 515 516 void setInventoryAssociation( 517 const std::shared_ptr<sdbusplus::asio::dbus_interface>& association, 518 const std::string& path, 519 const std::vector<std::string>& chassisPaths = std::vector<std::string>()) 520 { 521 if (association) 522 { 523 fs::path p(path); 524 std::vector<Association> associations; 525 std::string objPath(p.parent_path().string()); 526 527 associations.emplace_back("inventory", "sensors", objPath); 528 associations.emplace_back("chassis", "all_sensors", objPath); 529 530 for (const std::string& chassisPath : chassisPaths) 531 { 532 associations.emplace_back("chassis", "all_sensors", chassisPath); 533 } 534 535 association->register_property("Associations", associations); 536 association->initialize(); 537 } 538 } 539 540 void createInventoryAssoc( 541 const std::shared_ptr<sdbusplus::asio::connection>& conn, 542 const std::shared_ptr<sdbusplus::asio::dbus_interface>& association, 543 const std::string& path) 544 { 545 if (!association) 546 { 547 return; 548 } 549 550 conn->async_method_call( 551 [association, path](const boost::system::error_code ec, 552 const std::vector<std::string>& invSysObjPaths) { 553 if (ec) 554 { 555 // In case of error, set the default associations and 556 // initialize the association Interface. 557 setInventoryAssociation(association, path); 558 return; 559 } 560 setInventoryAssociation(association, path, invSysObjPaths); 561 }, 562 mapper::busName, mapper::path, mapper::interface, "GetSubTreePaths", 563 "/xyz/openbmc_project/inventory/system", 2, 564 std::array<std::string, 1>{ 565 "xyz.openbmc_project.Inventory.Item.System"}); 566 } 567 568 std::optional<double> readFile(const std::string& thresholdFile, 569 const double& scaleFactor) 570 { 571 std::string line; 572 std::ifstream labelFile(thresholdFile); 573 if (labelFile.good()) 574 { 575 std::getline(labelFile, line); 576 labelFile.close(); 577 578 try 579 { 580 return std::stod(line) / scaleFactor; 581 } 582 catch (const std::invalid_argument&) 583 { 584 return std::nullopt; 585 } 586 } 587 return std::nullopt; 588 } 589 590 std::optional<std::tuple<std::string, std::string, std::string>> 591 splitFileName(const fs::path& filePath) 592 { 593 if (filePath.has_filename()) 594 { 595 const auto fileName = filePath.filename().string(); 596 597 size_t numberPos = std::strcspn(fileName.c_str(), "1234567890"); 598 size_t itemPos = std::strcspn(fileName.c_str(), "_"); 599 600 if (numberPos > 0 && itemPos > numberPos && fileName.size() > itemPos) 601 { 602 return std::make_optional( 603 std::make_tuple(fileName.substr(0, numberPos), 604 fileName.substr(numberPos, itemPos - numberPos), 605 fileName.substr(itemPos + 1, fileName.size()))); 606 } 607 } 608 return std::nullopt; 609 } 610 611 static void handleSpecialModeChange(const std::string& manufacturingModeStatus) 612 { 613 manufacturingMode = false; 614 if (manufacturingModeStatus == "xyz.openbmc_project.Control.Security." 615 "SpecialMode.Modes.Manufacturing") 616 { 617 manufacturingMode = true; 618 } 619 if (validateUnsecureFeature == 1) 620 { 621 if (manufacturingModeStatus == "xyz.openbmc_project.Control.Security." 622 "SpecialMode.Modes.ValidationUnsecure") 623 { 624 manufacturingMode = true; 625 } 626 } 627 } 628 629 void setupManufacturingModeMatch(sdbusplus::asio::connection& conn) 630 { 631 namespace rules = sdbusplus::bus::match::rules; 632 static constexpr const char* specialModeInterface = 633 "xyz.openbmc_project.Security.SpecialMode"; 634 635 const std::string filterSpecialModeIntfAdd = 636 rules::interfacesAdded() + 637 rules::argNpath(0, "/xyz/openbmc_project/security/special_mode"); 638 static std::unique_ptr<sdbusplus::bus::match_t> specialModeIntfMatch = 639 std::make_unique<sdbusplus::bus::match_t>(conn, 640 filterSpecialModeIntfAdd, 641 [](sdbusplus::message_t& m) { 642 sdbusplus::message::object_path path; 643 using PropertyMap = 644 boost::container::flat_map<std::string, std::variant<std::string>>; 645 boost::container::flat_map<std::string, PropertyMap> interfaceAdded; 646 m.read(path, interfaceAdded); 647 auto intfItr = interfaceAdded.find(specialModeInterface); 648 if (intfItr == interfaceAdded.end()) 649 { 650 return; 651 } 652 PropertyMap& propertyList = intfItr->second; 653 auto itr = propertyList.find("SpecialMode"); 654 if (itr == propertyList.end()) 655 { 656 std::cerr << "error getting SpecialMode property " 657 << "\n"; 658 return; 659 } 660 auto* manufacturingModeStatus = std::get_if<std::string>(&itr->second); 661 handleSpecialModeChange(*manufacturingModeStatus); 662 }); 663 664 const std::string filterSpecialModeChange = 665 rules::type::signal() + rules::member("PropertiesChanged") + 666 rules::interface("org.freedesktop.DBus.Properties") + 667 rules::argN(0, specialModeInterface); 668 static std::unique_ptr<sdbusplus::bus::match_t> specialModeChangeMatch = 669 std::make_unique<sdbusplus::bus::match_t>(conn, filterSpecialModeChange, 670 [](sdbusplus::message_t& m) { 671 std::string interfaceName; 672 boost::container::flat_map<std::string, std::variant<std::string>> 673 propertiesChanged; 674 675 m.read(interfaceName, propertiesChanged); 676 auto itr = propertiesChanged.find("SpecialMode"); 677 if (itr == propertiesChanged.end()) 678 { 679 return; 680 } 681 auto* manufacturingModeStatus = std::get_if<std::string>(&itr->second); 682 handleSpecialModeChange(*manufacturingModeStatus); 683 }); 684 685 conn.async_method_call( 686 [](const boost::system::error_code ec, 687 const std::variant<std::string>& getManufactMode) { 688 if (ec) 689 { 690 std::cerr << "error getting SpecialMode status " << ec.message() 691 << "\n"; 692 return; 693 } 694 const auto* manufacturingModeStatus = 695 std::get_if<std::string>(&getManufactMode); 696 handleSpecialModeChange(*manufacturingModeStatus); 697 }, 698 "xyz.openbmc_project.SpecialMode", 699 "/xyz/openbmc_project/security/special_mode", 700 "org.freedesktop.DBus.Properties", "Get", specialModeInterface, 701 "SpecialMode"); 702 } 703 704 bool getManufacturingMode() 705 { 706 return manufacturingMode; 707 } 708 709 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> 710 setupPropertiesChangedMatches( 711 sdbusplus::asio::connection& bus, std::span<const char* const> types, 712 const std::function<void(sdbusplus::message_t&)>& handler) 713 { 714 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches; 715 for (const char* type : types) 716 { 717 auto match = std::make_unique<sdbusplus::bus::match_t>( 718 static_cast<sdbusplus::bus_t&>(bus), 719 "type='signal',member='PropertiesChanged',path_namespace='" + 720 std::string(inventoryPath) + "',arg0namespace='" + type + "'", 721 handler); 722 matches.emplace_back(std::move(match)); 723 } 724 return matches; 725 } 726