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, const std::string& matchString, 196 std::vector<fs::path>& foundPaths, int symlinkDepth) 197 { 198 if (!fs::exists(dirPath)) 199 { 200 return false; 201 } 202 203 std::regex search(matchString); 204 std::smatch match; 205 for (auto p = fs::recursive_directory_iterator( 206 dirPath, fs::directory_options::follow_directory_symlink); 207 p != fs::recursive_directory_iterator(); ++p) 208 { 209 std::string path = p->path().string(); 210 if (!is_directory(*p)) 211 { 212 if (std::regex_search(path, match, search)) 213 { 214 foundPaths.emplace_back(p->path()); 215 } 216 } 217 if (p.depth() >= symlinkDepth) 218 { 219 p.disable_recursion_pending(); 220 } 221 } 222 return true; 223 } 224 225 bool isPowerOn(void) 226 { 227 if (!powerMatch) 228 { 229 throw std::runtime_error("Power Match Not Created"); 230 } 231 return powerStatusOn; 232 } 233 234 bool hasBiosPost(void) 235 { 236 if (!postMatch) 237 { 238 throw std::runtime_error("Post Match Not Created"); 239 } 240 return biosHasPost; 241 } 242 243 static void 244 getPowerStatus(const std::shared_ptr<sdbusplus::asio::connection>& conn, 245 size_t retries = 2) 246 { 247 conn->async_method_call( 248 [conn, retries](boost::system::error_code ec, 249 const std::variant<std::string>& state) { 250 if (ec) 251 { 252 if (retries) 253 { 254 auto timer = std::make_shared<boost::asio::steady_timer>( 255 conn->get_io_context()); 256 timer->expires_after(std::chrono::seconds(15)); 257 timer->async_wait( 258 [timer, conn, retries](boost::system::error_code) { 259 getPowerStatus(conn, retries - 1); 260 }); 261 return; 262 } 263 264 // we commonly come up before power control, we'll capture the 265 // property change later 266 std::cerr << "error getting power status " << ec.message() 267 << "\n"; 268 return; 269 } 270 powerStatusOn = 271 boost::ends_with(std::get<std::string>(state), ".Running"); 272 }, 273 power::busname, power::path, properties::interface, properties::get, 274 power::interface, power::property); 275 } 276 277 static void 278 getPostStatus(const std::shared_ptr<sdbusplus::asio::connection>& conn, 279 size_t retries = 2) 280 { 281 conn->async_method_call( 282 [conn, retries](boost::system::error_code ec, 283 const std::variant<std::string>& state) { 284 if (ec) 285 { 286 if (retries) 287 { 288 auto timer = std::make_shared<boost::asio::steady_timer>( 289 conn->get_io_context()); 290 timer->expires_after(std::chrono::seconds(15)); 291 timer->async_wait( 292 [timer, conn, retries](boost::system::error_code) { 293 getPostStatus(conn, retries - 1); 294 }); 295 return; 296 } 297 // we commonly come up before power control, we'll capture the 298 // property change later 299 std::cerr << "error getting post status " << ec.message() 300 << "\n"; 301 return; 302 } 303 biosHasPost = std::get<std::string>(state) != "Inactive"; 304 }, 305 post::busname, post::path, properties::interface, properties::get, 306 post::interface, post::property); 307 } 308 309 void setupPowerMatch(const std::shared_ptr<sdbusplus::asio::connection>& conn) 310 { 311 static boost::asio::steady_timer timer(conn->get_io_context()); 312 // create a match for powergood changes, first time do a method call to 313 // cache the correct value 314 if (powerMatch) 315 { 316 return; 317 } 318 319 powerMatch = std::make_unique<sdbusplus::bus::match::match>( 320 static_cast<sdbusplus::bus::bus&>(*conn), 321 "type='signal',interface='" + std::string(properties::interface) + 322 "',path='" + std::string(power::path) + "',arg0='" + 323 std::string(power::interface) + "'", 324 [](sdbusplus::message::message& message) { 325 std::string objectName; 326 boost::container::flat_map<std::string, std::variant<std::string>> 327 values; 328 message.read(objectName, values); 329 auto findState = values.find(power::property); 330 if (findState != values.end()) 331 { 332 bool on = boost::ends_with( 333 std::get<std::string>(findState->second), ".Running"); 334 if (!on) 335 { 336 timer.cancel(); 337 powerStatusOn = false; 338 return; 339 } 340 // on comes too quickly 341 timer.expires_after(std::chrono::seconds(10)); 342 timer.async_wait([](boost::system::error_code ec) { 343 if (ec == boost::asio::error::operation_aborted) 344 { 345 return; 346 } 347 if (ec) 348 { 349 std::cerr << "Timer error " << ec.message() << "\n"; 350 return; 351 } 352 powerStatusOn = true; 353 }); 354 } 355 }); 356 357 postMatch = std::make_unique<sdbusplus::bus::match::match>( 358 static_cast<sdbusplus::bus::bus&>(*conn), 359 "type='signal',interface='" + std::string(properties::interface) + 360 "',path='" + std::string(post::path) + "',arg0='" + 361 std::string(post::interface) + "'", 362 [](sdbusplus::message::message& message) { 363 std::string objectName; 364 boost::container::flat_map<std::string, std::variant<std::string>> 365 values; 366 message.read(objectName, values); 367 auto findState = values.find(post::property); 368 if (findState != values.end()) 369 { 370 biosHasPost = 371 std::get<std::string>(findState->second) != "Inactive"; 372 } 373 }); 374 375 getPowerStatus(conn); 376 getPostStatus(conn); 377 } 378 379 // replaces limits if MinReading and MaxReading are found. 380 void findLimits(std::pair<double, double>& limits, 381 const SensorBaseConfiguration* data) 382 { 383 if (!data) 384 { 385 return; 386 } 387 auto maxFind = data->second.find("MaxReading"); 388 auto minFind = data->second.find("MinReading"); 389 390 if (minFind != data->second.end()) 391 { 392 limits.first = std::visit(VariantToDoubleVisitor(), minFind->second); 393 } 394 if (maxFind != data->second.end()) 395 { 396 limits.second = std::visit(VariantToDoubleVisitor(), maxFind->second); 397 } 398 } 399 400 void createAssociation( 401 std::shared_ptr<sdbusplus::asio::dbus_interface>& association, 402 const std::string& path) 403 { 404 if (association) 405 { 406 std::filesystem::path p(path); 407 408 std::vector<Association> associations; 409 associations.emplace_back("chassis", "all_sensors", 410 p.parent_path().string()); 411 association->register_property("Associations", associations); 412 association->initialize(); 413 } 414 } 415 416 void setInventoryAssociation( 417 const std::shared_ptr<sdbusplus::asio::dbus_interface>& association, 418 const std::string& path, 419 const std::vector<std::string>& chassisPaths = std::vector<std::string>()) 420 { 421 if (association) 422 { 423 std::filesystem::path p(path); 424 std::vector<Association> associations; 425 std::string objPath(p.parent_path().string()); 426 427 associations.emplace_back("inventory", "sensors", objPath); 428 associations.emplace_back("chassis", "all_sensors", objPath); 429 430 for (const std::string& chassisPath : chassisPaths) 431 { 432 associations.emplace_back("chassis", "all_sensors", chassisPath); 433 } 434 435 association->register_property("Associations", associations); 436 association->initialize(); 437 } 438 } 439 440 void createInventoryAssoc( 441 const std::shared_ptr<sdbusplus::asio::connection>& conn, 442 const std::shared_ptr<sdbusplus::asio::dbus_interface>& association, 443 const std::string& path) 444 { 445 if (!association) 446 { 447 return; 448 } 449 450 conn->async_method_call( 451 [association, path](const boost::system::error_code ec, 452 const std::vector<std::string>& invSysObjPaths) { 453 if (ec) 454 { 455 // In case of error, set the default associations and 456 // initialize the association Interface. 457 setInventoryAssociation(association, path); 458 return; 459 } 460 setInventoryAssociation(association, path, invSysObjPaths); 461 }, 462 mapper::busName, mapper::path, mapper::interface, "GetSubTreePaths", 463 "/xyz/openbmc_project/inventory/system", 2, 464 std::array<std::string, 1>{ 465 "xyz.openbmc_project.Inventory.Item.System"}); 466 } 467 468 std::optional<double> readFile(const std::string& thresholdFile, 469 const double& scaleFactor) 470 { 471 std::string line; 472 std::ifstream labelFile(thresholdFile); 473 if (labelFile.good()) 474 { 475 std::getline(labelFile, line); 476 labelFile.close(); 477 478 try 479 { 480 return std::stod(line) / scaleFactor; 481 } 482 catch (const std::invalid_argument&) 483 { 484 return std::nullopt; 485 } 486 } 487 return std::nullopt; 488 } 489 490 std::optional<std::tuple<std::string, std::string, std::string>> 491 splitFileName(const std::filesystem::path& filePath) 492 { 493 if (filePath.has_filename()) 494 { 495 const auto fileName = filePath.filename().string(); 496 497 size_t numberPos = std::strcspn(fileName.c_str(), "1234567890"); 498 size_t itemPos = std::strcspn(fileName.c_str(), "_"); 499 500 if (numberPos > 0 && itemPos > numberPos && fileName.size() > itemPos) 501 { 502 return std::make_optional( 503 std::make_tuple(fileName.substr(0, numberPos), 504 fileName.substr(numberPos, itemPos - numberPos), 505 fileName.substr(itemPos + 1, fileName.size()))); 506 } 507 } 508 return std::nullopt; 509 } 510 511 void setupManufacturingModeMatch(sdbusplus::asio::connection& conn) 512 { 513 static std::unique_ptr<sdbusplus::bus::match::match> 514 setupManufacturingModeMatch = 515 std::make_unique<sdbusplus::bus::match::match>( 516 conn, 517 "type='signal',interface='org.freedesktop.DBus." 518 "Properties',member='" 519 "PropertiesChanged',arg0namespace='xyz.openbmc_project." 520 "Security.SpecialMode'", 521 [](sdbusplus::message::message& msg) { 522 std::string interfaceName; 523 boost::container::flat_map<std::string, 524 std::variant<std::string>> 525 propertiesChanged; 526 std::string manufacturingModeStatus; 527 528 msg.read(interfaceName, propertiesChanged); 529 if (propertiesChanged.begin() == propertiesChanged.end()) 530 { 531 return; 532 } 533 534 manufacturingModeStatus = std::get<std::string>( 535 propertiesChanged.begin()->second); 536 manufacturingMode = false; 537 if (manufacturingModeStatus == 538 "xyz.openbmc_project.Control.Security." 539 "SpecialMode.Modes.Manufacturing") 540 { 541 manufacturingMode = true; 542 } 543 if (validateUnsecureFeature == true) 544 { 545 if (manufacturingModeStatus == 546 "xyz.openbmc_project.Control.Security." 547 "SpecialMode.Modes.ValidationUnsecure") 548 { 549 manufacturingMode = true; 550 } 551 } 552 }); 553 554 return; 555 } 556 557 bool getManufacturingMode() 558 { 559 return manufacturingMode; 560 }