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 setupPowerMatch(const std::shared_ptr<sdbusplus::asio::connection>& conn) 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 [](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 = boost::ends_with(std::get<std::string>(findState->second), 422 ".Running"); 423 if (!on) 424 { 425 timer.cancel(); 426 powerStatusOn = false; 427 return; 428 } 429 // on comes too quickly 430 timer.expires_after(std::chrono::seconds(10)); 431 timer.async_wait([](boost::system::error_code ec) { 432 if (ec == boost::asio::error::operation_aborted) 433 { 434 return; 435 } 436 if (ec) 437 { 438 std::cerr << "Timer error " << ec.message() << "\n"; 439 return; 440 } 441 powerStatusOn = true; 442 }); 443 } 444 }); 445 446 postMatch = std::make_unique<sdbusplus::bus::match_t>( 447 static_cast<sdbusplus::bus_t&>(*conn), 448 "type='signal',interface='" + std::string(properties::interface) + 449 "',path='" + std::string(post::path) + "',arg0='" + 450 std::string(post::interface) + "'", 451 [](sdbusplus::message_t& message) { 452 std::string objectName; 453 boost::container::flat_map<std::string, std::variant<std::string>> 454 values; 455 message.read(objectName, values); 456 auto findState = values.find(post::property); 457 if (findState != values.end()) 458 { 459 auto& value = std::get<std::string>(findState->second); 460 biosHasPost = (value != "Inactive") && 461 (value != "xyz.openbmc_project.State.OperatingSystem." 462 "Status.OSStatus.Inactive"); 463 } 464 }); 465 466 getPowerStatus(conn); 467 getPostStatus(conn); 468 } 469 470 // replaces limits if MinReading and MaxReading are found. 471 void findLimits(std::pair<double, double>& limits, 472 const SensorBaseConfiguration* data) 473 { 474 if (data == nullptr) 475 { 476 return; 477 } 478 auto maxFind = data->second.find("MaxReading"); 479 auto minFind = data->second.find("MinReading"); 480 481 if (minFind != data->second.end()) 482 { 483 limits.first = std::visit(VariantToDoubleVisitor(), minFind->second); 484 } 485 if (maxFind != data->second.end()) 486 { 487 limits.second = std::visit(VariantToDoubleVisitor(), maxFind->second); 488 } 489 } 490 491 void createAssociation( 492 std::shared_ptr<sdbusplus::asio::dbus_interface>& association, 493 const std::string& path) 494 { 495 if (association) 496 { 497 fs::path p(path); 498 499 std::vector<Association> associations; 500 associations.emplace_back("chassis", "all_sensors", 501 p.parent_path().string()); 502 association->register_property("Associations", associations); 503 association->initialize(); 504 } 505 } 506 507 void setInventoryAssociation( 508 const std::shared_ptr<sdbusplus::asio::dbus_interface>& association, 509 const std::string& path, 510 const std::vector<std::string>& chassisPaths = std::vector<std::string>()) 511 { 512 if (association) 513 { 514 fs::path p(path); 515 std::vector<Association> associations; 516 std::string objPath(p.parent_path().string()); 517 518 associations.emplace_back("inventory", "sensors", objPath); 519 associations.emplace_back("chassis", "all_sensors", objPath); 520 521 for (const std::string& chassisPath : chassisPaths) 522 { 523 associations.emplace_back("chassis", "all_sensors", chassisPath); 524 } 525 526 association->register_property("Associations", associations); 527 association->initialize(); 528 } 529 } 530 531 void createInventoryAssoc( 532 const std::shared_ptr<sdbusplus::asio::connection>& conn, 533 const std::shared_ptr<sdbusplus::asio::dbus_interface>& association, 534 const std::string& path) 535 { 536 if (!association) 537 { 538 return; 539 } 540 541 conn->async_method_call( 542 [association, path](const boost::system::error_code ec, 543 const std::vector<std::string>& invSysObjPaths) { 544 if (ec) 545 { 546 // In case of error, set the default associations and 547 // initialize the association Interface. 548 setInventoryAssociation(association, path); 549 return; 550 } 551 setInventoryAssociation(association, path, invSysObjPaths); 552 }, 553 mapper::busName, mapper::path, mapper::interface, "GetSubTreePaths", 554 "/xyz/openbmc_project/inventory/system", 2, 555 std::array<std::string, 1>{ 556 "xyz.openbmc_project.Inventory.Item.System"}); 557 } 558 559 std::optional<double> readFile(const std::string& thresholdFile, 560 const double& scaleFactor) 561 { 562 std::string line; 563 std::ifstream labelFile(thresholdFile); 564 if (labelFile.good()) 565 { 566 std::getline(labelFile, line); 567 labelFile.close(); 568 569 try 570 { 571 return std::stod(line) / scaleFactor; 572 } 573 catch (const std::invalid_argument&) 574 { 575 return std::nullopt; 576 } 577 } 578 return std::nullopt; 579 } 580 581 std::optional<std::tuple<std::string, std::string, std::string>> 582 splitFileName(const fs::path& filePath) 583 { 584 if (filePath.has_filename()) 585 { 586 const auto fileName = filePath.filename().string(); 587 588 size_t numberPos = std::strcspn(fileName.c_str(), "1234567890"); 589 size_t itemPos = std::strcspn(fileName.c_str(), "_"); 590 591 if (numberPos > 0 && itemPos > numberPos && fileName.size() > itemPos) 592 { 593 return std::make_optional( 594 std::make_tuple(fileName.substr(0, numberPos), 595 fileName.substr(numberPos, itemPos - numberPos), 596 fileName.substr(itemPos + 1, fileName.size()))); 597 } 598 } 599 return std::nullopt; 600 } 601 602 static void handleSpecialModeChange(const std::string& manufacturingModeStatus) 603 { 604 manufacturingMode = false; 605 if (manufacturingModeStatus == "xyz.openbmc_project.Control.Security." 606 "SpecialMode.Modes.Manufacturing") 607 { 608 manufacturingMode = true; 609 } 610 if (validateUnsecureFeature == 1) 611 { 612 if (manufacturingModeStatus == "xyz.openbmc_project.Control.Security." 613 "SpecialMode.Modes.ValidationUnsecure") 614 { 615 manufacturingMode = true; 616 } 617 } 618 } 619 620 void setupManufacturingModeMatch(sdbusplus::asio::connection& conn) 621 { 622 namespace rules = sdbusplus::bus::match::rules; 623 static constexpr const char* specialModeInterface = 624 "xyz.openbmc_project.Security.SpecialMode"; 625 626 const std::string filterSpecialModeIntfAdd = 627 rules::interfacesAdded() + 628 rules::argNpath(0, "/xyz/openbmc_project/security/special_mode"); 629 static std::unique_ptr<sdbusplus::bus::match_t> specialModeIntfMatch = 630 std::make_unique<sdbusplus::bus::match_t>(conn, 631 filterSpecialModeIntfAdd, 632 [](sdbusplus::message_t& m) { 633 sdbusplus::message::object_path path; 634 using PropertyMap = 635 boost::container::flat_map<std::string, std::variant<std::string>>; 636 boost::container::flat_map<std::string, PropertyMap> interfaceAdded; 637 m.read(path, interfaceAdded); 638 auto intfItr = interfaceAdded.find(specialModeInterface); 639 if (intfItr == interfaceAdded.end()) 640 { 641 return; 642 } 643 PropertyMap& propertyList = intfItr->second; 644 auto itr = propertyList.find("SpecialMode"); 645 if (itr == propertyList.end()) 646 { 647 std::cerr << "error getting SpecialMode property " 648 << "\n"; 649 return; 650 } 651 auto* manufacturingModeStatus = std::get_if<std::string>(&itr->second); 652 handleSpecialModeChange(*manufacturingModeStatus); 653 }); 654 655 const std::string filterSpecialModeChange = 656 rules::type::signal() + rules::member("PropertiesChanged") + 657 rules::interface("org.freedesktop.DBus.Properties") + 658 rules::argN(0, specialModeInterface); 659 static std::unique_ptr<sdbusplus::bus::match_t> specialModeChangeMatch = 660 std::make_unique<sdbusplus::bus::match_t>(conn, filterSpecialModeChange, 661 [](sdbusplus::message_t& m) { 662 std::string interfaceName; 663 boost::container::flat_map<std::string, std::variant<std::string>> 664 propertiesChanged; 665 666 m.read(interfaceName, propertiesChanged); 667 auto itr = propertiesChanged.find("SpecialMode"); 668 if (itr == propertiesChanged.end()) 669 { 670 return; 671 } 672 auto* manufacturingModeStatus = std::get_if<std::string>(&itr->second); 673 handleSpecialModeChange(*manufacturingModeStatus); 674 }); 675 676 conn.async_method_call( 677 [](const boost::system::error_code ec, 678 const std::variant<std::string>& getManufactMode) { 679 if (ec) 680 { 681 std::cerr << "error getting SpecialMode status " << ec.message() 682 << "\n"; 683 return; 684 } 685 const auto* manufacturingModeStatus = 686 std::get_if<std::string>(&getManufactMode); 687 handleSpecialModeChange(*manufacturingModeStatus); 688 }, 689 "xyz.openbmc_project.SpecialMode", 690 "/xyz/openbmc_project/security/special_mode", 691 "org.freedesktop.DBus.Properties", "Get", specialModeInterface, 692 "SpecialMode"); 693 } 694 695 bool getManufacturingMode() 696 { 697 return manufacturingMode; 698 } 699