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