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