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