1 #include "config.h" 2 3 #include "item_updater.hpp" 4 5 #include "images.hpp" 6 #include "serialize.hpp" 7 #include "version.hpp" 8 #include "xyz/openbmc_project/Software/Version/server.hpp" 9 10 #include <experimental/filesystem> 11 #include <fstream> 12 #include <phosphor-logging/elog-errors.hpp> 13 #include <phosphor-logging/elog.hpp> 14 #include <phosphor-logging/log.hpp> 15 #include <queue> 16 #include <set> 17 #include <string> 18 #include <xyz/openbmc_project/Common/error.hpp> 19 #include <xyz/openbmc_project/Software/Image/error.hpp> 20 21 namespace phosphor 22 { 23 namespace software 24 { 25 namespace updater 26 { 27 28 // When you see server:: you know we're referencing our base class 29 namespace server = sdbusplus::xyz::openbmc_project::Software::server; 30 namespace control = sdbusplus::xyz::openbmc_project::Control::server; 31 32 using namespace phosphor::logging; 33 using namespace sdbusplus::xyz::openbmc_project::Software::Image::Error; 34 using namespace phosphor::software::image; 35 namespace fs = std::experimental::filesystem; 36 using NotAllowed = sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed; 37 38 void ItemUpdater::createActivation(sdbusplus::message::message& msg) 39 { 40 41 using SVersion = server::Version; 42 using VersionPurpose = SVersion::VersionPurpose; 43 using VersionClass = phosphor::software::manager::Version; 44 namespace mesg = sdbusplus::message; 45 namespace variant_ns = mesg::variant_ns; 46 47 mesg::object_path objPath; 48 auto purpose = VersionPurpose::Unknown; 49 std::string version; 50 std::map<std::string, std::map<std::string, mesg::variant<std::string>>> 51 interfaces; 52 msg.read(objPath, interfaces); 53 std::string path(std::move(objPath)); 54 std::string filePath; 55 56 for (const auto& intf : interfaces) 57 { 58 if (intf.first == VERSION_IFACE) 59 { 60 for (const auto& property : intf.second) 61 { 62 if (property.first == "Purpose") 63 { 64 auto value = SVersion::convertVersionPurposeFromString( 65 variant_ns::get<std::string>(property.second)); 66 if (value == VersionPurpose::BMC || 67 value == VersionPurpose::System) 68 { 69 purpose = value; 70 } 71 } 72 else if (property.first == "Version") 73 { 74 version = variant_ns::get<std::string>(property.second); 75 } 76 } 77 } 78 else if (intf.first == FILEPATH_IFACE) 79 { 80 for (const auto& property : intf.second) 81 { 82 if (property.first == "Path") 83 { 84 filePath = variant_ns::get<std::string>(property.second); 85 } 86 } 87 } 88 } 89 if (version.empty() || filePath.empty() || 90 purpose == VersionPurpose::Unknown) 91 { 92 return; 93 } 94 95 // Version id is the last item in the path 96 auto pos = path.rfind("/"); 97 if (pos == std::string::npos) 98 { 99 log<level::ERR>("No version id found in object path", 100 entry("OBJPATH=%s", path.c_str())); 101 return; 102 } 103 104 auto versionId = path.substr(pos + 1); 105 106 if (activations.find(versionId) == activations.end()) 107 { 108 // Determine the Activation state by processing the given image dir. 109 auto activationState = server::Activation::Activations::Invalid; 110 ItemUpdater::ActivationStatus result = 111 ItemUpdater::validateSquashFSImage(filePath); 112 AssociationList associations = {}; 113 114 if (result == ItemUpdater::ActivationStatus::ready) 115 { 116 activationState = server::Activation::Activations::Ready; 117 // Create an association to the BMC inventory item 118 associations.emplace_back( 119 std::make_tuple(ACTIVATION_FWD_ASSOCIATION, 120 ACTIVATION_REV_ASSOCIATION, bmcInventoryPath)); 121 } 122 123 activations.insert(std::make_pair( 124 versionId, 125 std::make_unique<Activation>(bus, path, *this, versionId, 126 activationState, associations))); 127 128 auto versionPtr = std::make_unique<VersionClass>( 129 bus, path, version, purpose, filePath, 130 std::bind(&ItemUpdater::erase, this, std::placeholders::_1)); 131 versionPtr->deleteObject = 132 std::make_unique<phosphor::software::manager::Delete>(bus, path, 133 *versionPtr); 134 versions.insert(std::make_pair(versionId, std::move(versionPtr))); 135 } 136 return; 137 } 138 139 void ItemUpdater::processBMCImage() 140 { 141 using VersionClass = phosphor::software::manager::Version; 142 143 // Check MEDIA_DIR and create if it does not exist 144 try 145 { 146 if (!fs::is_directory(MEDIA_DIR)) 147 { 148 fs::create_directory(MEDIA_DIR); 149 } 150 } 151 catch (const fs::filesystem_error& e) 152 { 153 log<level::ERR>("Failed to prepare dir", entry("ERR=%s", e.what())); 154 return; 155 } 156 157 // Read os-release from /etc/ to get the functional BMC version 158 auto functionalVersion = VersionClass::getBMCVersion(OS_RELEASE_FILE); 159 160 // Read os-release from folders under /media/ to get 161 // BMC Software Versions. 162 for (const auto& iter : fs::directory_iterator(MEDIA_DIR)) 163 { 164 auto activationState = server::Activation::Activations::Active; 165 static const auto BMC_RO_PREFIX_LEN = strlen(BMC_ROFS_PREFIX); 166 167 // Check if the BMC_RO_PREFIXis the prefix of the iter.path 168 if (0 == 169 iter.path().native().compare(0, BMC_RO_PREFIX_LEN, BMC_ROFS_PREFIX)) 170 { 171 // The versionId is extracted from the path 172 // for example /media/ro-2a1022fe. 173 auto id = iter.path().native().substr(BMC_RO_PREFIX_LEN); 174 auto osRelease = iter.path() / OS_RELEASE_FILE; 175 if (!fs::is_regular_file(osRelease)) 176 { 177 log<level::ERR>( 178 "Failed to read osRelease", 179 entry("FILENAME=%s", osRelease.string().c_str())); 180 ItemUpdater::erase(id); 181 continue; 182 } 183 auto version = VersionClass::getBMCVersion(osRelease); 184 if (version.empty()) 185 { 186 log<level::ERR>( 187 "Failed to read version from osRelease", 188 entry("FILENAME=%s", osRelease.string().c_str())); 189 activationState = server::Activation::Activations::Invalid; 190 } 191 192 auto purpose = server::Version::VersionPurpose::BMC; 193 auto path = fs::path(SOFTWARE_OBJPATH) / id; 194 195 // Create functional association if this is the functional 196 // version 197 if (version.compare(functionalVersion) == 0) 198 { 199 createFunctionalAssociation(path); 200 } 201 202 AssociationList associations = {}; 203 204 if (activationState == server::Activation::Activations::Active) 205 { 206 // Create an association to the BMC inventory item 207 associations.emplace_back(std::make_tuple( 208 ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION, 209 bmcInventoryPath)); 210 211 // Create an active association since this image is active 212 createActiveAssociation(path); 213 } 214 215 // Create Version instance for this version. 216 auto versionPtr = std::make_unique<VersionClass>( 217 bus, path, version, purpose, "", 218 std::bind(&ItemUpdater::erase, this, std::placeholders::_1)); 219 auto isVersionFunctional = versionPtr->isFunctional(); 220 if (!isVersionFunctional) 221 { 222 versionPtr->deleteObject = 223 std::make_unique<phosphor::software::manager::Delete>( 224 bus, path, *versionPtr); 225 } 226 versions.insert(std::make_pair(id, std::move(versionPtr))); 227 228 // Create Activation instance for this version. 229 activations.insert(std::make_pair( 230 id, std::make_unique<Activation>( 231 bus, path, *this, id, activationState, associations))); 232 233 // If Active, create RedundancyPriority instance for this 234 // version. 235 if (activationState == server::Activation::Activations::Active) 236 { 237 uint8_t priority = std::numeric_limits<uint8_t>::max(); 238 if (!restoreFromFile(id, priority)) 239 { 240 if (isVersionFunctional) 241 { 242 priority = 0; 243 } 244 else 245 { 246 log<level::ERR>("Unable to restore priority from file.", 247 entry("VERSIONID=%s", id.c_str())); 248 } 249 } 250 activations.find(id)->second->redundancyPriority = 251 std::make_unique<RedundancyPriority>( 252 bus, path, *(activations.find(id)->second), priority, 253 false); 254 } 255 } 256 } 257 258 // If there is no ubi volume for bmc version then read the /etc/os-release 259 // and create rofs-<versionId> under /media 260 if (activations.size() == 0) 261 { 262 auto version = VersionClass::getBMCVersion(OS_RELEASE_FILE); 263 auto id = phosphor::software::manager::Version::getId(version); 264 auto versionFileDir = BMC_ROFS_PREFIX + id + "/etc/"; 265 try 266 { 267 if (!fs::is_directory(versionFileDir)) 268 { 269 fs::create_directories(versionFileDir); 270 } 271 auto versionFilePath = BMC_ROFS_PREFIX + id + OS_RELEASE_FILE; 272 fs::create_directory_symlink(OS_RELEASE_FILE, versionFilePath); 273 ItemUpdater::processBMCImage(); 274 } 275 catch (const std::exception& e) 276 { 277 log<level::ERR>(e.what()); 278 } 279 } 280 281 mirrorUbootToAlt(); 282 return; 283 } 284 285 void ItemUpdater::erase(std::string entryId) 286 { 287 // Find entry in versions map 288 auto it = versions.find(entryId); 289 if (it != versions.end()) 290 { 291 if (it->second->isFunctional() && ACTIVE_BMC_MAX_ALLOWED > 1) 292 { 293 log<level::ERR>("Error: Version is currently running on the BMC. " 294 "Unable to remove.", 295 entry("VERSIONID=%s", entryId.c_str())); 296 return; 297 } 298 299 // Delete ReadOnly partitions if it's not active 300 removeReadOnlyPartition(entryId); 301 removeFile(entryId); 302 303 // Removing entry in versions map 304 this->versions.erase(entryId); 305 } 306 else 307 { 308 // Delete ReadOnly partitions even if we can't find the version 309 removeReadOnlyPartition(entryId); 310 removeFile(entryId); 311 312 log<level::ERR>("Error: Failed to find version in item updater " 313 "versions map. Unable to remove.", 314 entry("VERSIONID=%s", entryId.c_str())); 315 } 316 317 helper.clearEntry(entryId); 318 319 // Removing entry in activations map 320 auto ita = activations.find(entryId); 321 if (ita == activations.end()) 322 { 323 log<level::ERR>("Error: Failed to find version in item updater " 324 "activations map. Unable to remove.", 325 entry("VERSIONID=%s", entryId.c_str())); 326 } 327 else 328 { 329 removeAssociations(ita->second->path); 330 this->activations.erase(entryId); 331 } 332 ItemUpdater::resetUbootEnvVars(); 333 return; 334 } 335 336 void ItemUpdater::deleteAll() 337 { 338 std::vector<std::string> deletableVersions; 339 340 for (const auto& versionIt : versions) 341 { 342 if (!versionIt.second->isFunctional()) 343 { 344 deletableVersions.push_back(versionIt.first); 345 } 346 } 347 348 for (const auto& deletableIt : deletableVersions) 349 { 350 ItemUpdater::erase(deletableIt); 351 } 352 353 helper.cleanup(); 354 } 355 356 ItemUpdater::ActivationStatus 357 ItemUpdater::validateSquashFSImage(const std::string& filePath) 358 { 359 bool invalid = false; 360 361 for (auto& bmcImage : bmcImages) 362 { 363 fs::path file(filePath); 364 file /= bmcImage; 365 std::ifstream efile(file.c_str()); 366 if (efile.good() != 1) 367 { 368 log<level::ERR>("Failed to find the BMC image.", 369 entry("IMAGE=%s", bmcImage.c_str())); 370 invalid = true; 371 } 372 } 373 374 if (invalid) 375 { 376 return ItemUpdater::ActivationStatus::invalid; 377 } 378 379 return ItemUpdater::ActivationStatus::ready; 380 } 381 382 void ItemUpdater::savePriority(const std::string& versionId, uint8_t value) 383 { 384 storeToFile(versionId, value); 385 helper.setEntry(versionId, value); 386 } 387 388 void ItemUpdater::freePriority(uint8_t value, const std::string& versionId) 389 { 390 std::map<std::string, uint8_t> priorityMap; 391 392 // Insert the requested version and priority, it may not exist yet. 393 priorityMap.insert(std::make_pair(versionId, value)); 394 395 for (const auto& intf : activations) 396 { 397 if (intf.second->redundancyPriority) 398 { 399 priorityMap.insert(std::make_pair( 400 intf.first, intf.second->redundancyPriority.get()->priority())); 401 } 402 } 403 404 // Lambda function to compare 2 priority values, use <= to allow duplicates 405 typedef std::function<bool(std::pair<std::string, uint8_t>, 406 std::pair<std::string, uint8_t>)> 407 cmpPriority; 408 cmpPriority cmpPriorityFunc = 409 [](std::pair<std::string, uint8_t> priority1, 410 std::pair<std::string, uint8_t> priority2) { 411 return priority1.second <= priority2.second; 412 }; 413 414 // Sort versions by ascending priority 415 std::set<std::pair<std::string, uint8_t>, cmpPriority> prioritySet( 416 priorityMap.begin(), priorityMap.end(), cmpPriorityFunc); 417 418 auto freePriorityValue = value; 419 for (auto& element : prioritySet) 420 { 421 if (element.first == versionId) 422 { 423 continue; 424 } 425 if (element.second == freePriorityValue) 426 { 427 ++freePriorityValue; 428 auto it = activations.find(element.first); 429 it->second->redundancyPriority.get()->sdbusPriority( 430 freePriorityValue); 431 } 432 } 433 434 auto lowestVersion = prioritySet.begin()->first; 435 if (value == prioritySet.begin()->second) 436 { 437 lowestVersion = versionId; 438 } 439 updateUbootEnvVars(lowestVersion); 440 } 441 442 void ItemUpdater::reset() 443 { 444 helper.factoryReset(); 445 446 log<level::INFO>("BMC factory reset will take effect upon reboot."); 447 } 448 449 void ItemUpdater::removeReadOnlyPartition(std::string versionId) 450 { 451 helper.removeVersion(versionId); 452 } 453 454 bool ItemUpdater::fieldModeEnabled(bool value) 455 { 456 // enabling field mode is intended to be one way: false -> true 457 if (value && !control::FieldMode::fieldModeEnabled()) 458 { 459 control::FieldMode::fieldModeEnabled(value); 460 461 helper.enableFieldMode(); 462 } 463 else if (!value && control::FieldMode::fieldModeEnabled()) 464 { 465 elog<NotAllowed>(xyz::openbmc_project::Common::NotAllowed::REASON( 466 "FieldMode is not allowed to be cleared")); 467 } 468 469 return control::FieldMode::fieldModeEnabled(); 470 } 471 472 void ItemUpdater::restoreFieldModeStatus() 473 { 474 std::ifstream input("/dev/mtd/u-boot-env"); 475 std::string envVar; 476 std::getline(input, envVar); 477 478 if (envVar.find("fieldmode=true") != std::string::npos) 479 { 480 ItemUpdater::fieldModeEnabled(true); 481 } 482 } 483 484 void ItemUpdater::setBMCInventoryPath() 485 { 486 auto depth = 0; 487 auto mapperCall = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH, 488 MAPPER_INTERFACE, "GetSubTreePaths"); 489 490 mapperCall.append(INVENTORY_PATH); 491 mapperCall.append(depth); 492 std::vector<std::string> filter = {BMC_INVENTORY_INTERFACE}; 493 mapperCall.append(filter); 494 495 try 496 { 497 auto response = bus.call(mapperCall); 498 499 using ObjectPaths = std::vector<std::string>; 500 ObjectPaths result; 501 response.read(result); 502 503 if (!result.empty()) 504 { 505 bmcInventoryPath = result.front(); 506 } 507 } 508 catch (const sdbusplus::exception::SdBusError& e) 509 { 510 log<level::ERR>("Error in mapper GetSubTreePath"); 511 return; 512 } 513 514 return; 515 } 516 517 void ItemUpdater::createActiveAssociation(const std::string& path) 518 { 519 assocs.emplace_back( 520 std::make_tuple(ACTIVE_FWD_ASSOCIATION, ACTIVE_REV_ASSOCIATION, path)); 521 associations(assocs); 522 } 523 524 void ItemUpdater::createFunctionalAssociation(const std::string& path) 525 { 526 assocs.emplace_back(std::make_tuple(FUNCTIONAL_FWD_ASSOCIATION, 527 FUNCTIONAL_REV_ASSOCIATION, path)); 528 associations(assocs); 529 } 530 531 void ItemUpdater::removeAssociations(const std::string& path) 532 { 533 for (auto iter = assocs.begin(); iter != assocs.end();) 534 { 535 if ((std::get<2>(*iter)).compare(path) == 0) 536 { 537 iter = assocs.erase(iter); 538 associations(assocs); 539 } 540 else 541 { 542 ++iter; 543 } 544 } 545 } 546 547 bool ItemUpdater::isLowestPriority(uint8_t value) 548 { 549 for (const auto& intf : activations) 550 { 551 if (intf.second->redundancyPriority) 552 { 553 if (intf.second->redundancyPriority.get()->priority() < value) 554 { 555 return false; 556 } 557 } 558 } 559 return true; 560 } 561 562 void ItemUpdater::updateUbootEnvVars(const std::string& versionId) 563 { 564 helper.updateUbootVersionId(versionId); 565 } 566 567 void ItemUpdater::resetUbootEnvVars() 568 { 569 decltype(activations.begin()->second->redundancyPriority.get()->priority()) 570 lowestPriority = std::numeric_limits<uint8_t>::max(); 571 decltype(activations.begin()->second->versionId) lowestPriorityVersion; 572 for (const auto& intf : activations) 573 { 574 if (!intf.second->redundancyPriority.get()) 575 { 576 // Skip this version if the redundancyPriority is not initialized. 577 continue; 578 } 579 580 if (intf.second->redundancyPriority.get()->priority() <= lowestPriority) 581 { 582 lowestPriority = intf.second->redundancyPriority.get()->priority(); 583 lowestPriorityVersion = intf.second->versionId; 584 } 585 } 586 587 // Update the U-boot environment variable to point to the lowest priority 588 updateUbootEnvVars(lowestPriorityVersion); 589 } 590 591 void ItemUpdater::freeSpace(Activation& caller) 592 { 593 // Versions with the highest priority in front 594 std::priority_queue<std::pair<int, std::string>, 595 std::vector<std::pair<int, std::string>>, 596 std::less<std::pair<int, std::string>>> 597 versionsPQ; 598 599 std::size_t count = 0; 600 for (const auto& iter : activations) 601 { 602 if ((iter.second.get()->activation() == 603 server::Activation::Activations::Active) || 604 (iter.second.get()->activation() == 605 server::Activation::Activations::Failed)) 606 { 607 count++; 608 // Don't put the functional version on the queue since we can't 609 // remove the "running" BMC version. 610 // If ACTIVE_BMC_MAX_ALLOWED <= 1, there is only one active BMC, 611 // so remove functional version as well. 612 // Don't delete the the Activation object that called this function. 613 if ((versions.find(iter.second->versionId) 614 ->second->isFunctional() && 615 ACTIVE_BMC_MAX_ALLOWED > 1) || 616 (iter.second->versionId == caller.versionId)) 617 { 618 continue; 619 } 620 621 // Failed activations don't have priority, assign them a large value 622 // for sorting purposes. 623 auto priority = 999; 624 if (iter.second.get()->activation() == 625 server::Activation::Activations::Active) 626 { 627 priority = iter.second->redundancyPriority.get()->priority(); 628 } 629 630 versionsPQ.push(std::make_pair(priority, iter.second->versionId)); 631 } 632 } 633 634 // If the number of BMC versions is over ACTIVE_BMC_MAX_ALLOWED -1, 635 // remove the highest priority one(s). 636 while ((count >= ACTIVE_BMC_MAX_ALLOWED) && (!versionsPQ.empty())) 637 { 638 erase(versionsPQ.top().second); 639 versionsPQ.pop(); 640 count--; 641 } 642 } 643 644 void ItemUpdater::mirrorUbootToAlt() 645 { 646 helper.mirrorAlt(); 647 } 648 649 } // namespace updater 650 } // namespace software 651 } // namespace phosphor 652