1 #include "config.h" 2 3 #include "item_updater.hpp" 4 5 #include "utils.hpp" 6 7 #include <cassert> 8 #include <filesystem> 9 #include <phosphor-logging/elog-errors.hpp> 10 #include <phosphor-logging/log.hpp> 11 #include <xyz/openbmc_project/Common/error.hpp> 12 13 namespace 14 { 15 constexpr auto MANIFEST_VERSION = "version"; 16 constexpr auto MANIFEST_EXTENDED_VERSION = "extended_version"; 17 } // namespace 18 19 namespace phosphor 20 { 21 namespace software 22 { 23 namespace updater 24 { 25 namespace server = sdbusplus::xyz::openbmc_project::Software::server; 26 27 using namespace sdbusplus::xyz::openbmc_project::Common::Error; 28 using namespace phosphor::logging; 29 using SVersion = server::Version; 30 using VersionPurpose = SVersion::VersionPurpose; 31 32 void ItemUpdater::createActivation(sdbusplus::message::message& m) 33 { 34 namespace msg = sdbusplus::message; 35 namespace variant_ns = msg::variant_ns; 36 37 sdbusplus::message::object_path objPath; 38 std::map<std::string, std::map<std::string, msg::variant<std::string>>> 39 interfaces; 40 m.read(objPath, interfaces); 41 42 std::string path(std::move(objPath)); 43 std::string filePath; 44 auto purpose = VersionPurpose::Unknown; 45 std::string version; 46 47 for (const auto& [interfaceName, propertyMap] : interfaces) 48 { 49 if (interfaceName == VERSION_IFACE) 50 { 51 for (const auto& [propertyName, propertyValue] : propertyMap) 52 { 53 if (propertyName == "Purpose") 54 { 55 // Only process the PSU images 56 auto value = SVersion::convertVersionPurposeFromString( 57 variant_ns::get<std::string>(propertyValue)); 58 59 if (value == VersionPurpose::PSU) 60 { 61 purpose = value; 62 } 63 } 64 else if (propertyName == VERSION) 65 { 66 version = variant_ns::get<std::string>(propertyValue); 67 } 68 } 69 } 70 else if (interfaceName == FILEPATH_IFACE) 71 { 72 const auto& it = propertyMap.find("Path"); 73 if (it != propertyMap.end()) 74 { 75 filePath = variant_ns::get<std::string>(it->second); 76 } 77 } 78 } 79 if ((filePath.empty()) || (purpose == VersionPurpose::Unknown)) 80 { 81 return; 82 } 83 84 // Version id is the last item in the path 85 auto pos = path.rfind("/"); 86 if (pos == std::string::npos) 87 { 88 log<level::ERR>("No version id found in object path", 89 entry("OBJPATH=%s", path.c_str())); 90 return; 91 } 92 93 auto versionId = path.substr(pos + 1); 94 95 if (activations.find(versionId) == activations.end()) 96 { 97 // Determine the Activation state by processing the given image dir. 98 AssociationList associations; 99 auto activationState = Activation::Status::Ready; 100 101 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION, 102 ACTIVATION_REV_ASSOCIATION, 103 PSU_INVENTORY_PATH_BASE)); 104 105 fs::path manifestPath(filePath); 106 manifestPath /= MANIFEST_FILE; 107 std::string extendedVersion = 108 Version::getValue(manifestPath, {MANIFEST_EXTENDED_VERSION}); 109 110 auto activation = 111 createActivationObject(path, versionId, extendedVersion, 112 activationState, associations, filePath); 113 activations.emplace(versionId, std::move(activation)); 114 115 auto versionPtr = 116 createVersionObject(path, versionId, version, purpose); 117 versions.emplace(versionId, std::move(versionPtr)); 118 } 119 return; 120 } 121 122 void ItemUpdater::erase(const std::string& versionId) 123 { 124 auto it = versions.find(versionId); 125 if (it == versions.end()) 126 { 127 log<level::ERR>(("Error: Failed to find version " + versionId + 128 " in item updater versions map." 129 " Unable to remove.") 130 .c_str()); 131 } 132 else 133 { 134 versions.erase(versionId); 135 } 136 137 // Removing entry in activations map 138 auto ita = activations.find(versionId); 139 if (ita == activations.end()) 140 { 141 log<level::ERR>(("Error: Failed to find version " + versionId + 142 " in item updater activations map." 143 " Unable to remove.") 144 .c_str()); 145 } 146 else 147 { 148 activations.erase(versionId); 149 } 150 } 151 152 void ItemUpdater::createActiveAssociation(const std::string& path) 153 { 154 assocs.emplace_back( 155 std::make_tuple(ACTIVE_FWD_ASSOCIATION, ACTIVE_REV_ASSOCIATION, path)); 156 associations(assocs); 157 } 158 159 void ItemUpdater::addFunctionalAssociation(const std::string& path) 160 { 161 assocs.emplace_back(std::make_tuple(FUNCTIONAL_FWD_ASSOCIATION, 162 FUNCTIONAL_REV_ASSOCIATION, path)); 163 associations(assocs); 164 } 165 166 void ItemUpdater::removeAssociation(const std::string& path) 167 { 168 for (auto iter = assocs.begin(); iter != assocs.end();) 169 { 170 if ((std::get<2>(*iter)).compare(path) == 0) 171 { 172 iter = assocs.erase(iter); 173 associations(assocs); 174 } 175 else 176 { 177 ++iter; 178 } 179 } 180 } 181 182 std::unique_ptr<Activation> ItemUpdater::createActivationObject( 183 const std::string& path, const std::string& versionId, 184 const std::string& extVersion, Activation::Status activationStatus, 185 const AssociationList& assocs, const std::string& filePath) 186 { 187 return std::make_unique<Activation>(bus, path, versionId, extVersion, 188 activationStatus, assocs, this, 189 filePath); 190 } 191 192 void ItemUpdater::createPsuObject(const std::string& psuInventoryPath, 193 const std::string& psuVersion) 194 { 195 auto versionId = utils::getVersionId(psuVersion); 196 auto path = std::string(SOFTWARE_OBJPATH) + "/" + versionId; 197 198 auto it = activations.find(versionId); 199 if (it != activations.end()) 200 { 201 // The versionId is already created, associate the path 202 auto associations = it->second->associations(); 203 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION, 204 ACTIVATION_REV_ASSOCIATION, 205 psuInventoryPath)); 206 it->second->associations(associations); 207 psuPathActivationMap.emplace(psuInventoryPath, it->second); 208 } 209 else 210 { 211 // Create a new object for running PSU inventory 212 AssociationList associations; 213 auto activationState = Activation::Status::Active; 214 215 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION, 216 ACTIVATION_REV_ASSOCIATION, 217 psuInventoryPath)); 218 219 auto activation = createActivationObject( 220 path, versionId, "", activationState, associations, ""); 221 activations.emplace(versionId, std::move(activation)); 222 psuPathActivationMap.emplace(psuInventoryPath, activations[versionId]); 223 224 auto versionPtr = createVersionObject(path, versionId, psuVersion, 225 VersionPurpose::PSU); 226 versions.emplace(versionId, std::move(versionPtr)); 227 228 createActiveAssociation(path); 229 addFunctionalAssociation(path); 230 } 231 } 232 233 void ItemUpdater::removePsuObject(const std::string& psuInventoryPath) 234 { 235 auto it = psuPathActivationMap.find(psuInventoryPath); 236 if (it == psuPathActivationMap.end()) 237 { 238 log<level::ERR>("No Activation found for PSU", 239 entry("PSUPATH=%s", psuInventoryPath.c_str())); 240 return; 241 } 242 const auto& activationPtr = it->second; 243 psuPathActivationMap.erase(psuInventoryPath); 244 245 auto associations = activationPtr->associations(); 246 for (auto iter = associations.begin(); iter != associations.end();) 247 { 248 if ((std::get<2>(*iter)).compare(psuInventoryPath) == 0) 249 { 250 iter = associations.erase(iter); 251 } 252 else 253 { 254 ++iter; 255 } 256 } 257 if (associations.empty()) 258 { 259 // Remove the activation 260 erase(activationPtr->getVersionId()); 261 } 262 else 263 { 264 // Update association 265 activationPtr->associations(associations); 266 } 267 } 268 269 std::unique_ptr<Version> ItemUpdater::createVersionObject( 270 const std::string& objPath, const std::string& versionId, 271 const std::string& versionString, 272 sdbusplus::xyz::openbmc_project::Software::server::Version::VersionPurpose 273 versionPurpose) 274 { 275 versionStrings.insert(versionString); 276 auto version = std::make_unique<Version>( 277 bus, objPath, versionId, versionString, versionPurpose, 278 std::bind(&ItemUpdater::erase, this, std::placeholders::_1)); 279 return version; 280 } 281 282 void ItemUpdater::onPsuInventoryChangedMsg(sdbusplus::message::message& msg) 283 { 284 using Interface = std::string; 285 Interface interface; 286 Properties properties; 287 std::string psuPath = msg.get_path(); 288 289 msg.read(interface, properties); 290 onPsuInventoryChanged(psuPath, properties); 291 } 292 293 void ItemUpdater::onPsuInventoryChanged(const std::string& psuPath, 294 const Properties& properties) 295 { 296 bool present; 297 std::string version; 298 299 // Only present property is interested 300 auto p = properties.find(PRESENT); 301 if (p == properties.end()) 302 { 303 return; 304 } 305 present = sdbusplus::message::variant_ns::get<bool>(p->second); 306 307 if (present) 308 { 309 version = utils::getVersion(psuPath); 310 if (!version.empty()) 311 { 312 createPsuObject(psuPath, version); 313 } 314 } 315 else 316 { 317 // Remove object or association 318 removePsuObject(psuPath); 319 } 320 } 321 322 void ItemUpdater::processPSUImage() 323 { 324 auto paths = utils::getPSUInventoryPath(bus); 325 for (const auto& p : paths) 326 { 327 auto service = utils::getService(bus, p.c_str(), ITEM_IFACE); 328 auto present = utils::getProperty<bool>(bus, service.c_str(), p.c_str(), 329 ITEM_IFACE, PRESENT); 330 auto version = utils::getVersion(p); 331 if (present && !version.empty()) 332 { 333 createPsuObject(p, version); 334 } 335 // Add matches for PSU Inventory's property changes 336 psuMatches.emplace_back( 337 bus, MatchRules::propertiesChanged(p, ITEM_IFACE), 338 std::bind(&ItemUpdater::onPsuInventoryChangedMsg, this, 339 std::placeholders::_1)); 340 } 341 } 342 343 void ItemUpdater::processStoredImage() 344 { 345 scanDirectory(IMG_DIR_BUILTIN); 346 scanDirectory(IMG_DIR_PERSIST); 347 } 348 349 void ItemUpdater::scanDirectory(const fs::path& dir) 350 { 351 // The directory shall put PSU images in directories named with model 352 if (!fs::exists(dir)) 353 { 354 // Skip 355 return; 356 } 357 if (!fs::is_directory(dir)) 358 { 359 log<level::ERR>("The path is not a directory", 360 entry("PATH=%s", dir.c_str())); 361 return; 362 } 363 for (const auto& d : fs::directory_iterator(dir)) 364 { 365 // If the model in manifest does not match the dir name 366 // Log a warning and skip it 367 auto path = d.path(); 368 auto manifest = path / MANIFEST_FILE; 369 if (fs::exists(manifest)) 370 { 371 auto ret = Version::getValues( 372 manifest.string(), 373 {MANIFEST_VERSION, MANIFEST_EXTENDED_VERSION}); 374 auto version = ret[MANIFEST_VERSION]; 375 auto extVersion = ret[MANIFEST_EXTENDED_VERSION]; 376 auto info = Version::getExtVersionInfo(extVersion); 377 auto model = info["model"]; 378 if (path.stem() != model) 379 { 380 log<level::ERR>("Unmatched model", 381 entry("PATH=%s", path.c_str()), 382 entry("MODEL=%s", model.c_str())); 383 continue; 384 } 385 auto versionId = utils::getVersionId(version); 386 auto it = activations.find(versionId); 387 if (it == activations.end()) 388 { 389 // This is a version that is different than the running PSUs 390 auto activationState = Activation::Status::Ready; 391 auto purpose = VersionPurpose::PSU; 392 auto objPath = std::string(SOFTWARE_OBJPATH) + "/" + versionId; 393 394 auto activation = createActivationObject( 395 objPath, versionId, extVersion, activationState, {}, path); 396 activations.emplace(versionId, std::move(activation)); 397 398 auto versionPtr = 399 createVersionObject(objPath, versionId, version, purpose); 400 versions.emplace(versionId, std::move(versionPtr)); 401 } 402 else 403 { 404 // This is a version that a running PSU is using, set the path 405 // on the version object 406 it->second->path(path); 407 } 408 } 409 else 410 { 411 log<level::ERR>("No MANIFEST found", 412 entry("PATH=%s", path.c_str())); 413 } 414 } 415 } 416 417 std::optional<std::string> ItemUpdater::getLatestVersionId() 418 { 419 auto latestVersion = utils::getLatestVersion(versionStrings); 420 if (latestVersion.empty()) 421 { 422 return {}; 423 } 424 425 std::optional<std::string> versionId; 426 for (const auto& v : versions) 427 { 428 if (v.second->version() == latestVersion) 429 { 430 versionId = v.first; 431 break; 432 } 433 } 434 assert(versionId.has_value()); 435 return versionId; 436 } 437 438 void ItemUpdater::syncToLatestImage() 439 { 440 auto latestVersionId = getLatestVersionId(); 441 if (!latestVersionId) 442 { 443 return; 444 } 445 const auto& it = activations.find(*latestVersionId); 446 assert(it != activations.end()); 447 const auto& activation = it->second; 448 const auto& assocs = activation->associations(); 449 450 auto paths = utils::getPSUInventoryPath(bus); 451 for (const auto& p : paths) 452 { 453 // As long as there is a PSU is not associated with the latest image, 454 // run the activation so that all PSUs are running the same latest 455 // image. 456 if (!utils::isAssociated(p, assocs)) 457 { 458 log<level::INFO>("Automatically update PSU", 459 entry("VERSION_ID=%s", latestVersionId->c_str())); 460 invokeActivation(activation); 461 break; 462 } 463 } 464 } 465 466 void ItemUpdater::invokeActivation( 467 const std::unique_ptr<Activation>& activation) 468 { 469 activation->requestedActivation(Activation::RequestedActivations::Active); 470 } 471 472 } // namespace updater 473 } // namespace software 474 } // namespace phosphor 475