1 #include "config.h" 2 3 #include "item_updater.hpp" 4 5 #include "utils.hpp" 6 7 #include <filesystem> 8 #include <phosphor-logging/elog-errors.hpp> 9 #include <phosphor-logging/log.hpp> 10 #include <xyz/openbmc_project/Common/error.hpp> 11 12 namespace phosphor 13 { 14 namespace software 15 { 16 namespace updater 17 { 18 namespace server = sdbusplus::xyz::openbmc_project::Software::server; 19 namespace fs = std::filesystem; 20 21 using namespace sdbusplus::xyz::openbmc_project::Common::Error; 22 using namespace phosphor::logging; 23 using SVersion = server::Version; 24 using VersionPurpose = SVersion::VersionPurpose; 25 26 void ItemUpdater::createActivation(sdbusplus::message::message& m) 27 { 28 namespace msg = sdbusplus::message; 29 namespace variant_ns = msg::variant_ns; 30 31 sdbusplus::message::object_path objPath; 32 std::map<std::string, std::map<std::string, msg::variant<std::string>>> 33 interfaces; 34 m.read(objPath, interfaces); 35 36 std::string path(std::move(objPath)); 37 std::string filePath; 38 auto purpose = VersionPurpose::Unknown; 39 std::string version; 40 41 for (const auto& [interfaceName, propertyMap] : interfaces) 42 { 43 if (interfaceName == VERSION_IFACE) 44 { 45 for (const auto& [propertyName, propertyValue] : propertyMap) 46 { 47 if (propertyName == "Purpose") 48 { 49 // Only process the PSU images 50 auto value = SVersion::convertVersionPurposeFromString( 51 variant_ns::get<std::string>(propertyValue)); 52 53 if (value == VersionPurpose::PSU) 54 { 55 purpose = value; 56 } 57 } 58 else if (propertyName == VERSION) 59 { 60 version = variant_ns::get<std::string>(propertyValue); 61 } 62 } 63 } 64 else if (interfaceName == FILEPATH_IFACE) 65 { 66 const auto& it = propertyMap.find("Path"); 67 if (it != propertyMap.end()) 68 { 69 filePath = variant_ns::get<std::string>(it->second); 70 } 71 } 72 } 73 if ((filePath.empty()) || (purpose == VersionPurpose::Unknown)) 74 { 75 return; 76 } 77 78 // Version id is the last item in the path 79 auto pos = path.rfind("/"); 80 if (pos == std::string::npos) 81 { 82 log<level::ERR>("No version id found in object path", 83 entry("OBJPATH=%s", path.c_str())); 84 return; 85 } 86 87 auto versionId = path.substr(pos + 1); 88 89 if (activations.find(versionId) == activations.end()) 90 { 91 // Determine the Activation state by processing the given image dir. 92 AssociationList associations; 93 auto activationState = server::Activation::Activations::Ready; 94 95 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION, 96 ACTIVATION_REV_ASSOCIATION, 97 PSU_INVENTORY_PATH_BASE)); 98 99 fs::path manifestPath(filePath); 100 manifestPath /= MANIFEST_FILE; 101 std::string extendedVersion = 102 (Version::getValue( 103 manifestPath.string(), 104 std::map<std::string, std::string>{{"extended_version", ""}})) 105 .begin() 106 ->second; 107 108 auto activation = createActivationObject( 109 path, versionId, extendedVersion, activationState, associations); 110 activations.emplace(versionId, std::move(activation)); 111 112 auto versionPtr = 113 createVersionObject(path, versionId, version, purpose, filePath); 114 versions.emplace(versionId, std::move(versionPtr)); 115 } 116 return; 117 } 118 119 void ItemUpdater::erase(std::string versionId) 120 { 121 auto it = versions.find(versionId); 122 if (it == versions.end()) 123 { 124 log<level::ERR>(("Error: Failed to find version " + versionId + 125 " in item updater versions map." 126 " Unable to remove.") 127 .c_str()); 128 } 129 else 130 { 131 versions.erase(versionId); 132 } 133 134 // Removing entry in activations map 135 auto ita = activations.find(versionId); 136 if (ita == activations.end()) 137 { 138 log<level::ERR>(("Error: Failed to find version " + versionId + 139 " in item updater activations map." 140 " Unable to remove.") 141 .c_str()); 142 } 143 else 144 { 145 activations.erase(versionId); 146 } 147 } 148 149 void ItemUpdater::deleteAll() 150 { 151 // TODO: when PSU's running firmware is implemented, delete all versions 152 // that are not the running firmware. 153 } 154 155 void ItemUpdater::createActiveAssociation(const std::string& path) 156 { 157 assocs.emplace_back( 158 std::make_tuple(ACTIVE_FWD_ASSOCIATION, ACTIVE_REV_ASSOCIATION, path)); 159 associations(assocs); 160 } 161 162 void ItemUpdater::addFunctionalAssociation(const std::string& path) 163 { 164 assocs.emplace_back(std::make_tuple(FUNCTIONAL_FWD_ASSOCIATION, 165 FUNCTIONAL_REV_ASSOCIATION, path)); 166 associations(assocs); 167 } 168 169 void ItemUpdater::removeAssociation(const std::string& path) 170 { 171 for (auto iter = assocs.begin(); iter != assocs.end();) 172 { 173 if ((std::get<2>(*iter)).compare(path) == 0) 174 { 175 iter = assocs.erase(iter); 176 associations(assocs); 177 } 178 else 179 { 180 ++iter; 181 } 182 } 183 } 184 185 std::unique_ptr<Activation> ItemUpdater::createActivationObject( 186 const std::string& path, const std::string& versionId, 187 const std::string& extVersion, 188 sdbusplus::xyz::openbmc_project::Software::server::Activation::Activations 189 activationStatus, 190 const AssociationList& assocs) 191 { 192 return std::make_unique<Activation>(bus, path, versionId, extVersion, 193 activationStatus, assocs); 194 } 195 196 void ItemUpdater::createPsuObject(const std::string& psuInventoryPath, 197 const std::string& psuVersion) 198 { 199 auto versionId = utils::getVersionId(psuVersion); 200 auto path = std::string(SOFTWARE_OBJPATH) + "/" + versionId; 201 202 psuStatusMap[psuInventoryPath] = {true, psuVersion}; 203 204 auto it = activations.find(versionId); 205 if (it != activations.end()) 206 { 207 // The versionId is already created, associate the path 208 auto associations = it->second->associations(); 209 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION, 210 ACTIVATION_REV_ASSOCIATION, 211 psuInventoryPath)); 212 it->second->associations(associations); 213 psuPathActivationMap.emplace(psuInventoryPath, it->second); 214 } 215 else 216 { 217 // Create a new object for running PSU inventory 218 AssociationList associations; 219 auto activationState = server::Activation::Activations::Active; 220 221 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION, 222 ACTIVATION_REV_ASSOCIATION, 223 psuInventoryPath)); 224 225 auto activation = createActivationObject(path, versionId, "", 226 activationState, associations); 227 activations.emplace(versionId, std::move(activation)); 228 psuPathActivationMap.emplace(psuInventoryPath, activations[versionId]); 229 230 auto versionPtr = createVersionObject(path, versionId, psuVersion, 231 VersionPurpose::PSU, ""); 232 versions.emplace(versionId, std::move(versionPtr)); 233 234 createActiveAssociation(path); 235 addFunctionalAssociation(path); 236 } 237 } 238 239 void ItemUpdater::removePsuObject(const std::string& psuInventoryPath) 240 { 241 psuStatusMap[psuInventoryPath] = {false, ""}; 242 auto it = psuPathActivationMap.find(psuInventoryPath); 243 if (it == psuPathActivationMap.end()) 244 { 245 log<level::ERR>("No Activation found for PSU", 246 entry("PSUPATH=%s", psuInventoryPath.c_str())); 247 return; 248 } 249 const auto& activationPtr = it->second; 250 psuPathActivationMap.erase(psuInventoryPath); 251 252 auto associations = activationPtr->associations(); 253 for (auto iter = associations.begin(); iter != associations.end();) 254 { 255 if ((std::get<2>(*iter)).compare(psuInventoryPath) == 0) 256 { 257 iter = associations.erase(iter); 258 } 259 else 260 { 261 ++iter; 262 } 263 } 264 if (associations.empty()) 265 { 266 // Remove the activation 267 erase(activationPtr->versionId); 268 } 269 else 270 { 271 // Update association 272 activationPtr->associations(associations); 273 } 274 } 275 276 std::unique_ptr<Version> ItemUpdater::createVersionObject( 277 const std::string& objPath, const std::string& versionId, 278 const std::string& versionString, 279 sdbusplus::xyz::openbmc_project::Software::server::Version::VersionPurpose 280 versionPurpose, 281 const std::string& filePath) 282 { 283 auto version = std::make_unique<Version>( 284 bus, objPath, versionId, versionString, versionPurpose, filePath, 285 std::bind(&ItemUpdater::erase, this, std::placeholders::_1)); 286 return version; 287 } 288 289 void ItemUpdater::onPsuInventoryChangedMsg(sdbusplus::message::message& msg) 290 { 291 using Interface = std::string; 292 Interface interface; 293 Properties properties; 294 std::string psuPath = msg.get_path(); 295 296 msg.read(interface, properties); 297 onPsuInventoryChanged(psuPath, properties); 298 } 299 300 void ItemUpdater::onPsuInventoryChanged(const std::string& psuPath, 301 const Properties& properties) 302 { 303 std::optional<bool> present; 304 std::optional<std::string> version; 305 306 // The code was expecting to get callback on mutliple properties changed. 307 // But in practice, the callback is received one-by-one for each property. 308 // So it has to handle Present and Version property separately. 309 auto p = properties.find(PRESENT); 310 if (p != properties.end()) 311 { 312 present = sdbusplus::message::variant_ns::get<bool>(p->second); 313 psuStatusMap[psuPath].present = *present; 314 } 315 p = properties.find(VERSION); 316 if (p != properties.end()) 317 { 318 version = sdbusplus::message::variant_ns::get<std::string>(p->second); 319 psuStatusMap[psuPath].version = *version; 320 } 321 322 // If present or version is not changed, ignore 323 if (!present.has_value() && !version.has_value()) 324 { 325 return; 326 } 327 328 if (psuStatusMap[psuPath].present) 329 { 330 // If version is not updated, let's wait for it 331 if (psuStatusMap[psuPath].version.empty()) 332 { 333 log<level::DEBUG>("Waiting for version to be updated"); 334 return; 335 } 336 // Create object or association based on the version and psu inventory 337 // path 338 createPsuObject(psuPath, psuStatusMap[psuPath].version); 339 } 340 else 341 { 342 if (!present.has_value()) 343 { 344 // If a PSU is plugged out, version property is update to empty as 345 // well, and we get callback here, but ignore that because it is 346 // handled by PRESENT callback. 347 return; 348 } 349 // Remove object or association 350 removePsuObject(psuPath); 351 } 352 } 353 354 void ItemUpdater::processPSUImage() 355 { 356 auto paths = utils::getPSUInventoryPath(bus); 357 for (const auto& p : paths) 358 { 359 auto service = utils::getService(bus, p.c_str(), ITEM_IFACE); 360 auto present = utils::getProperty<bool>(bus, service.c_str(), p.c_str(), 361 ITEM_IFACE, PRESENT); 362 auto version = utils::getVersion(p); 363 if (present && !version.empty()) 364 { 365 createPsuObject(p, version); 366 } 367 // Add matches for PSU Inventory's property changes 368 psuMatches.emplace_back( 369 bus, 370 MatchRules::type::signal() + MatchRules::path(p) + 371 MatchRules::member("PropertiesChanged") + 372 MatchRules::interface("org.freedesktop.DBus.Properties"), 373 std::bind(&ItemUpdater::onPsuInventoryChangedMsg, this, 374 std::placeholders::_1)); 375 } 376 } 377 378 } // namespace updater 379 } // namespace software 380 } // namespace phosphor 381