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 <Utils.hpp> 18 #include <boost/algorithm/string/predicate.hpp> 19 #include <boost/container/flat_map.hpp> 20 #include <sdbusplus/asio/connection.hpp> 21 #include <sdbusplus/asio/object_server.hpp> 22 #include <sdbusplus/bus/match.hpp> 23 24 #include <filesystem> 25 #include <fstream> 26 #include <memory> 27 #include <regex> 28 #include <stdexcept> 29 #include <string> 30 #include <utility> 31 #include <variant> 32 #include <vector> 33 34 namespace fs = std::filesystem; 35 36 static bool powerStatusOn = false; 37 static bool biosHasPost = false; 38 39 static std::unique_ptr<sdbusplus::bus::match::match> powerMatch = nullptr; 40 static std::unique_ptr<sdbusplus::bus::match::match> postMatch = nullptr; 41 42 /** 43 * return the contents of a file 44 * @param[in] hwmonFile - the path to the file to read 45 * @return the contents of the file as a string or nullopt if the file could not 46 * be opened. 47 */ 48 49 std::optional<std::string> openAndRead(const std::string& hwmonFile) 50 { 51 std::string fileVal; 52 std::ifstream fileStream(hwmonFile); 53 if (!fileStream.is_open()) 54 { 55 return std::nullopt; 56 } 57 std::getline(fileStream, fileVal); 58 return fileVal; 59 } 60 61 /** 62 * given a hwmon temperature base name if valid return the full path else 63 * nullopt 64 * @param[in] directory - the hwmon sysfs directory 65 * @param[in] permitSet - a set of labels or hwmon basenames to permit. If this 66 * is empty then *everything* is permitted. 67 * @return a string to the full path of the file to create a temp sensor with or 68 * nullopt to indicate that no sensor should be created for this basename. 69 */ 70 std::optional<std::string> 71 getFullHwmonFilePath(const std::string& directory, 72 const std::string& hwmonBaseName, 73 const std::set<std::string>& permitSet) 74 { 75 std::optional<std::string> result; 76 std::string filename; 77 if (permitSet.empty()) 78 { 79 result = directory + "/" + hwmonBaseName + "_input"; 80 return result; 81 } 82 filename = directory + "/" + hwmonBaseName + "_label"; 83 auto searchVal = openAndRead(filename); 84 if (!searchVal) 85 { 86 /* if the hwmon temp doesn't have a corresponding label file 87 * then use the hwmon temperature base name 88 */ 89 searchVal = hwmonBaseName; 90 } 91 if (permitSet.find(*searchVal) != permitSet.end()) 92 { 93 result = directory + "/" + hwmonBaseName + "_input"; 94 } 95 return result; 96 } 97 98 /** 99 * retrieve a set of basenames and labels to allow sensor creation for. 100 * @param[in] config - a map representing the configuration for a specific 101 * device 102 * @return a set of basenames and labels to allow sensor creation for. An empty 103 * set indicates that everything is permitted. 104 */ 105 std::set<std::string> getPermitSet(const SensorBaseConfigMap& config) 106 { 107 auto permitAttribute = config.find("Labels"); 108 std::set<std::string> permitSet; 109 if (permitAttribute != config.end()) 110 { 111 try 112 { 113 auto val = 114 std::get<std::vector<std::string>>(permitAttribute->second); 115 116 permitSet.insert(std::make_move_iterator(val.begin()), 117 std::make_move_iterator(val.end())); 118 } 119 catch (const std::bad_variant_access& err) 120 { 121 std::cerr << err.what() 122 << ":PermitList does not contain a list, wrong " 123 "variant type.\n"; 124 } 125 } 126 return permitSet; 127 } 128 129 bool getSensorConfiguration( 130 const std::string& type, 131 const std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, 132 ManagedObjectType& resp) 133 { 134 return getSensorConfiguration(type, dbusConnection, resp, false); 135 } 136 137 bool getSensorConfiguration( 138 const std::string& type, 139 const std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, 140 ManagedObjectType& resp, bool useCache) 141 { 142 static ManagedObjectType managedObj; 143 144 if (!useCache) 145 { 146 managedObj.clear(); 147 sdbusplus::message::message getManagedObjects = 148 dbusConnection->new_method_call( 149 entityManagerName, "/", "org.freedesktop.DBus.ObjectManager", 150 "GetManagedObjects"); 151 bool err = false; 152 try 153 { 154 sdbusplus::message::message reply = 155 dbusConnection->call(getManagedObjects); 156 reply.read(managedObj); 157 } 158 catch (const sdbusplus::exception::exception& e) 159 { 160 std::cerr << "While calling GetManagedObjects on service:" 161 << entityManagerName << " exception name:" << e.name() 162 << "and description:" << e.description() 163 << " was thrown\n"; 164 err = true; 165 } 166 167 if (err) 168 { 169 std::cerr << "Error communicating to entity manager\n"; 170 return false; 171 } 172 } 173 for (const auto& pathPair : managedObj) 174 { 175 bool correctType = false; 176 for (const auto& entry : pathPair.second) 177 { 178 if (boost::starts_with(entry.first, type)) 179 { 180 correctType = true; 181 break; 182 } 183 } 184 if (correctType) 185 { 186 resp.emplace(pathPair); 187 } 188 } 189 return true; 190 } 191 192 bool findFiles(const fs::path& dirPath, const std::string& matchString, 193 std::vector<fs::path>& foundPaths, int symlinkDepth) 194 { 195 if (!fs::exists(dirPath)) 196 { 197 return false; 198 } 199 200 std::regex search(matchString); 201 std::smatch match; 202 for (auto p = fs::recursive_directory_iterator( 203 dirPath, fs::directory_options::follow_directory_symlink); 204 p != fs::recursive_directory_iterator(); ++p) 205 { 206 std::string path = p->path().string(); 207 if (!is_directory(*p)) 208 { 209 if (std::regex_search(path, match, search)) 210 { 211 foundPaths.emplace_back(p->path()); 212 } 213 } 214 if (p.depth() >= symlinkDepth) 215 { 216 p.disable_recursion_pending(); 217 } 218 } 219 return true; 220 } 221 222 bool isPowerOn(void) 223 { 224 if (!powerMatch) 225 { 226 throw std::runtime_error("Power Match Not Created"); 227 } 228 return powerStatusOn; 229 } 230 231 bool hasBiosPost(void) 232 { 233 if (!postMatch) 234 { 235 throw std::runtime_error("Post Match Not Created"); 236 } 237 return biosHasPost; 238 } 239 240 static void 241 getPowerStatus(const std::shared_ptr<sdbusplus::asio::connection>& conn, 242 size_t retries = 2) 243 { 244 conn->async_method_call( 245 [conn, retries](boost::system::error_code ec, 246 const std::variant<std::string>& state) { 247 if (ec) 248 { 249 if (retries) 250 { 251 auto timer = std::make_shared<boost::asio::steady_timer>( 252 conn->get_io_context()); 253 timer->expires_after(std::chrono::seconds(15)); 254 timer->async_wait( 255 [timer, conn, retries](boost::system::error_code) { 256 getPowerStatus(conn, retries - 1); 257 }); 258 return; 259 } 260 261 // we commonly come up before power control, we'll capture the 262 // property change later 263 std::cerr << "error getting power status " << ec.message() 264 << "\n"; 265 return; 266 } 267 powerStatusOn = 268 boost::ends_with(std::get<std::string>(state), "Running"); 269 }, 270 power::busname, power::path, properties::interface, properties::get, 271 power::interface, power::property); 272 } 273 274 static void 275 getPostStatus(const std::shared_ptr<sdbusplus::asio::connection>& conn, 276 size_t retries = 2) 277 { 278 conn->async_method_call( 279 [conn, retries](boost::system::error_code ec, 280 const std::variant<std::string>& state) { 281 if (ec) 282 { 283 if (retries) 284 { 285 auto timer = std::make_shared<boost::asio::steady_timer>( 286 conn->get_io_context()); 287 timer->expires_after(std::chrono::seconds(15)); 288 timer->async_wait( 289 [timer, conn, retries](boost::system::error_code) { 290 getPostStatus(conn, retries - 1); 291 }); 292 return; 293 } 294 // we commonly come up before power control, we'll capture the 295 // property change later 296 std::cerr << "error getting post status " << ec.message() 297 << "\n"; 298 return; 299 } 300 biosHasPost = std::get<std::string>(state) != "Inactive"; 301 }, 302 post::busname, post::path, properties::interface, properties::get, 303 post::interface, post::property); 304 } 305 306 void setupPowerMatch(const std::shared_ptr<sdbusplus::asio::connection>& conn) 307 { 308 static boost::asio::steady_timer timer(conn->get_io_context()); 309 // create a match for powergood changes, first time do a method call to 310 // cache the correct value 311 if (powerMatch) 312 { 313 return; 314 } 315 316 powerMatch = std::make_unique<sdbusplus::bus::match::match>( 317 static_cast<sdbusplus::bus::bus&>(*conn), 318 "type='signal',interface='" + std::string(properties::interface) + 319 "',path='" + std::string(power::path) + "',arg0='" + 320 std::string(power::interface) + "'", 321 [](sdbusplus::message::message& message) { 322 std::string objectName; 323 boost::container::flat_map<std::string, std::variant<std::string>> 324 values; 325 message.read(objectName, values); 326 auto findState = values.find(power::property); 327 if (findState != values.end()) 328 { 329 bool on = boost::ends_with( 330 std::get<std::string>(findState->second), "Running"); 331 if (!on) 332 { 333 timer.cancel(); 334 powerStatusOn = false; 335 return; 336 } 337 // on comes too quickly 338 timer.expires_after(std::chrono::seconds(10)); 339 timer.async_wait([](boost::system::error_code ec) { 340 if (ec == boost::asio::error::operation_aborted) 341 { 342 return; 343 } 344 if (ec) 345 { 346 std::cerr << "Timer error " << ec.message() << "\n"; 347 return; 348 } 349 powerStatusOn = true; 350 }); 351 } 352 }); 353 354 postMatch = std::make_unique<sdbusplus::bus::match::match>( 355 static_cast<sdbusplus::bus::bus&>(*conn), 356 "type='signal',interface='" + std::string(properties::interface) + 357 "',path='" + std::string(post::path) + "',arg0='" + 358 std::string(post::interface) + "'", 359 [](sdbusplus::message::message& message) { 360 std::string objectName; 361 boost::container::flat_map<std::string, std::variant<std::string>> 362 values; 363 message.read(objectName, values); 364 auto findState = values.find(post::property); 365 if (findState != values.end()) 366 { 367 biosHasPost = 368 std::get<std::string>(findState->second) != "Inactive"; 369 } 370 }); 371 372 getPowerStatus(conn); 373 getPostStatus(conn); 374 } 375 376 // replaces limits if MinReading and MaxReading are found. 377 void findLimits(std::pair<double, double>& limits, 378 const SensorBaseConfiguration* data) 379 { 380 if (!data) 381 { 382 return; 383 } 384 auto maxFind = data->second.find("MaxReading"); 385 auto minFind = data->second.find("MinReading"); 386 387 if (minFind != data->second.end()) 388 { 389 limits.first = std::visit(VariantToDoubleVisitor(), minFind->second); 390 } 391 if (maxFind != data->second.end()) 392 { 393 limits.second = std::visit(VariantToDoubleVisitor(), maxFind->second); 394 } 395 } 396 397 void createAssociation( 398 std::shared_ptr<sdbusplus::asio::dbus_interface>& association, 399 const std::string& path) 400 { 401 if (association) 402 { 403 std::filesystem::path p(path); 404 405 std::vector<Association> associations; 406 associations.emplace_back("chassis", "all_sensors", 407 p.parent_path().string()); 408 association->register_property("Associations", associations); 409 association->initialize(); 410 } 411 } 412 413 void setInventoryAssociation( 414 const std::shared_ptr<sdbusplus::asio::dbus_interface>& association, 415 const std::string& path, 416 const std::vector<std::string>& chassisPaths = std::vector<std::string>()) 417 { 418 if (association) 419 { 420 std::filesystem::path p(path); 421 std::vector<Association> associations; 422 std::string objPath(p.parent_path().string()); 423 424 associations.emplace_back("inventory", "sensors", objPath); 425 associations.emplace_back("chassis", "all_sensors", objPath); 426 427 for (const std::string& chassisPath : chassisPaths) 428 { 429 associations.emplace_back("chassis", "all_sensors", chassisPath); 430 } 431 432 association->register_property("Associations", associations); 433 association->initialize(); 434 } 435 } 436 437 void createInventoryAssoc( 438 const std::shared_ptr<sdbusplus::asio::connection>& conn, 439 const std::shared_ptr<sdbusplus::asio::dbus_interface>& association, 440 const std::string& path) 441 { 442 if (!association) 443 { 444 return; 445 } 446 447 conn->async_method_call( 448 [association, path](const boost::system::error_code ec, 449 const std::vector<std::string>& invSysObjPaths) { 450 if (ec) 451 { 452 // In case of error, set the default associations and 453 // initialize the association Interface. 454 setInventoryAssociation(association, path); 455 return; 456 } 457 setInventoryAssociation(association, path, invSysObjPaths); 458 }, 459 mapper::busName, mapper::path, mapper::interface, "GetSubTreePaths", 460 "/xyz/openbmc_project/inventory/system", 2, 461 std::array<std::string, 1>{ 462 "xyz.openbmc_project.Inventory.Item.System"}); 463 } 464 465 std::optional<double> readFile(const std::string& thresholdFile, 466 const double& scaleFactor) 467 { 468 std::string line; 469 std::ifstream labelFile(thresholdFile); 470 if (labelFile.good()) 471 { 472 std::getline(labelFile, line); 473 labelFile.close(); 474 475 try 476 { 477 return std::stod(line) / scaleFactor; 478 } 479 catch (const std::invalid_argument&) 480 { 481 return std::nullopt; 482 } 483 } 484 return std::nullopt; 485 } 486 487 std::optional<std::tuple<std::string, std::string, std::string>> 488 splitFileName(const std::filesystem::path& filePath) 489 { 490 if (filePath.has_filename()) 491 { 492 const auto fileName = filePath.filename().string(); 493 494 size_t numberPos = std::strcspn(fileName.c_str(), "1234567890"); 495 size_t itemPos = std::strcspn(fileName.c_str(), "_"); 496 497 if (numberPos > 0 && itemPos > numberPos && fileName.size() > itemPos) 498 { 499 return std::make_optional( 500 std::make_tuple(fileName.substr(0, numberPos), 501 fileName.substr(numberPos, itemPos - numberPos), 502 fileName.substr(itemPos + 1, fileName.size()))); 503 } 504 } 505 return std::nullopt; 506 } 507