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