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