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 // The versionId is extracted from the path 179 // for example /media/ro-2a1022fe. 180 auto id = iter.path().native().substr(BMC_RO_PREFIX_LEN); 181 fs::path releaseFile(OS_RELEASE_FILE); 182 auto osRelease = iter.path() / releaseFile.relative_path(); 183 if (!fs::is_regular_file(osRelease)) 184 { 185 log<level::ERR>( 186 "Failed to read osRelease", 187 entry("FILENAME=%s", osRelease.string().c_str())); 188 ItemUpdater::erase(id); 189 continue; 190 } 191 auto version = VersionClass::getBMCVersion(osRelease); 192 if (version.empty()) 193 { 194 log<level::ERR>( 195 "Failed to read version from osRelease", 196 entry("FILENAME=%s", osRelease.string().c_str())); 197 activationState = server::Activation::Activations::Invalid; 198 } 199 200 auto purpose = server::Version::VersionPurpose::BMC; 201 restorePurpose(id, purpose); 202 203 auto path = fs::path(SOFTWARE_OBJPATH) / id; 204 205 // Create functional association if this is the functional 206 // version 207 if (version.compare(functionalVersion) == 0) 208 { 209 createFunctionalAssociation(path); 210 } 211 212 AssociationList associations = {}; 213 214 if (activationState == server::Activation::Activations::Active) 215 { 216 // Create an association to the BMC inventory item 217 associations.emplace_back(std::make_tuple( 218 ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION, 219 bmcInventoryPath)); 220 221 // Create an active association since this image is active 222 createActiveAssociation(path); 223 } 224 225 // All updateable firmware components must expose the updateable 226 // association. 227 createUpdateableAssociation(path); 228 229 // Create Version instance for this version. 230 auto versionPtr = std::make_unique<VersionClass>( 231 bus, path, version, purpose, "", 232 std::bind(&ItemUpdater::erase, this, std::placeholders::_1)); 233 auto isVersionFunctional = versionPtr->isFunctional(); 234 if (!isVersionFunctional) 235 { 236 versionPtr->deleteObject = 237 std::make_unique<phosphor::software::manager::Delete>( 238 bus, path, *versionPtr); 239 } 240 versions.insert(std::make_pair(id, std::move(versionPtr))); 241 242 // Create Activation instance for this version. 243 activations.insert(std::make_pair( 244 id, std::make_unique<Activation>( 245 bus, path, *this, id, activationState, associations))); 246 247 // If Active, create RedundancyPriority instance for this 248 // version. 249 if (activationState == server::Activation::Activations::Active) 250 { 251 uint8_t priority = std::numeric_limits<uint8_t>::max(); 252 if (!restorePriority(id, priority)) 253 { 254 if (isVersionFunctional) 255 { 256 priority = 0; 257 } 258 else 259 { 260 log<level::ERR>("Unable to restore priority from file.", 261 entry("VERSIONID=%s", id.c_str())); 262 } 263 } 264 activations.find(id)->second->redundancyPriority = 265 std::make_unique<RedundancyPriority>( 266 bus, path, *(activations.find(id)->second), priority, 267 false); 268 } 269 } 270 } 271 272 // If there is no ubi volume for bmc version then read the /etc/os-release 273 // and create rofs-<versionId> under /media 274 if (activations.size() == 0) 275 { 276 auto version = VersionClass::getBMCVersion(OS_RELEASE_FILE); 277 auto id = phosphor::software::manager::Version::getId(version); 278 auto versionFileDir = BMC_ROFS_PREFIX + id + "/etc/"; 279 try 280 { 281 if (!fs::is_directory(versionFileDir)) 282 { 283 fs::create_directories(versionFileDir); 284 } 285 auto versionFilePath = BMC_ROFS_PREFIX + id + OS_RELEASE_FILE; 286 fs::create_directory_symlink(OS_RELEASE_FILE, versionFilePath); 287 ItemUpdater::processBMCImage(); 288 } 289 catch (const std::exception& e) 290 { 291 log<level::ERR>(e.what()); 292 } 293 } 294 295 mirrorUbootToAlt(); 296 return; 297 } 298 299 void ItemUpdater::erase(std::string entryId) 300 { 301 // Find entry in versions map 302 auto it = versions.find(entryId); 303 if (it != versions.end()) 304 { 305 if (it->second->isFunctional() && ACTIVE_BMC_MAX_ALLOWED > 1) 306 { 307 log<level::ERR>("Error: Version is currently running on the BMC. " 308 "Unable to remove.", 309 entry("VERSIONID=%s", entryId.c_str())); 310 return; 311 } 312 313 // Delete ReadOnly partitions if it's not active 314 removeReadOnlyPartition(entryId); 315 removePersistDataDirectory(entryId); 316 317 // Removing entry in versions map 318 this->versions.erase(entryId); 319 } 320 else 321 { 322 // Delete ReadOnly partitions even if we can't find the version 323 removeReadOnlyPartition(entryId); 324 removePersistDataDirectory(entryId); 325 326 log<level::ERR>("Error: Failed to find version in item updater " 327 "versions map. Unable to remove.", 328 entry("VERSIONID=%s", entryId.c_str())); 329 } 330 331 helper.clearEntry(entryId); 332 333 // Removing entry in activations map 334 auto ita = activations.find(entryId); 335 if (ita == activations.end()) 336 { 337 log<level::ERR>("Error: Failed to find version in item updater " 338 "activations map. Unable to remove.", 339 entry("VERSIONID=%s", entryId.c_str())); 340 } 341 else 342 { 343 removeAssociations(ita->second->path); 344 this->activations.erase(entryId); 345 } 346 ItemUpdater::resetUbootEnvVars(); 347 return; 348 } 349 350 void ItemUpdater::deleteAll() 351 { 352 std::vector<std::string> deletableVersions; 353 354 for (const auto& versionIt : versions) 355 { 356 if (!versionIt.second->isFunctional()) 357 { 358 deletableVersions.push_back(versionIt.first); 359 } 360 } 361 362 for (const auto& deletableIt : deletableVersions) 363 { 364 ItemUpdater::erase(deletableIt); 365 } 366 367 helper.cleanup(); 368 } 369 370 ItemUpdater::ActivationStatus 371 ItemUpdater::validateSquashFSImage(const std::string& filePath) 372 { 373 bool invalid = false; 374 375 for (auto& bmcImage : bmcImages) 376 { 377 fs::path file(filePath); 378 file /= bmcImage; 379 std::ifstream efile(file.c_str()); 380 if (efile.good() != 1) 381 { 382 log<level::ERR>("Failed to find the BMC image.", 383 entry("IMAGE=%s", bmcImage.c_str())); 384 invalid = true; 385 } 386 } 387 388 if (invalid) 389 { 390 return ItemUpdater::ActivationStatus::invalid; 391 } 392 393 return ItemUpdater::ActivationStatus::ready; 394 } 395 396 void ItemUpdater::savePriority(const std::string& versionId, uint8_t value) 397 { 398 storePriority(versionId, value); 399 helper.setEntry(versionId, value); 400 } 401 402 void ItemUpdater::freePriority(uint8_t value, const std::string& versionId) 403 { 404 std::map<std::string, uint8_t> priorityMap; 405 406 // Insert the requested version and priority, it may not exist yet. 407 priorityMap.insert(std::make_pair(versionId, value)); 408 409 for (const auto& intf : activations) 410 { 411 if (intf.second->redundancyPriority) 412 { 413 priorityMap.insert(std::make_pair( 414 intf.first, intf.second->redundancyPriority.get()->priority())); 415 } 416 } 417 418 // Lambda function to compare 2 priority values, use <= to allow duplicates 419 typedef std::function<bool(std::pair<std::string, uint8_t>, 420 std::pair<std::string, uint8_t>)> 421 cmpPriority; 422 cmpPriority cmpPriorityFunc = 423 [](std::pair<std::string, uint8_t> priority1, 424 std::pair<std::string, uint8_t> priority2) { 425 return priority1.second <= priority2.second; 426 }; 427 428 // Sort versions by ascending priority 429 std::set<std::pair<std::string, uint8_t>, cmpPriority> prioritySet( 430 priorityMap.begin(), priorityMap.end(), cmpPriorityFunc); 431 432 auto freePriorityValue = value; 433 for (auto& element : prioritySet) 434 { 435 if (element.first == versionId) 436 { 437 continue; 438 } 439 if (element.second == freePriorityValue) 440 { 441 ++freePriorityValue; 442 auto it = activations.find(element.first); 443 it->second->redundancyPriority.get()->sdbusPriority( 444 freePriorityValue); 445 } 446 } 447 448 auto lowestVersion = prioritySet.begin()->first; 449 if (value == prioritySet.begin()->second) 450 { 451 lowestVersion = versionId; 452 } 453 updateUbootEnvVars(lowestVersion); 454 } 455 456 void ItemUpdater::reset() 457 { 458 constexpr auto setFactoryResetWait = std::chrono::seconds(3); 459 helper.factoryReset(); 460 461 // Need to wait for env variables to complete, otherwise an immediate reboot 462 // will not factory reset. 463 std::this_thread::sleep_for(setFactoryResetWait); 464 465 log<level::INFO>("BMC factory reset will take effect upon reboot."); 466 } 467 468 void ItemUpdater::removeReadOnlyPartition(std::string versionId) 469 { 470 helper.removeVersion(versionId); 471 } 472 473 bool ItemUpdater::fieldModeEnabled(bool value) 474 { 475 // enabling field mode is intended to be one way: false -> true 476 if (value && !control::FieldMode::fieldModeEnabled()) 477 { 478 control::FieldMode::fieldModeEnabled(value); 479 480 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, 481 SYSTEMD_INTERFACE, "StartUnit"); 482 method.append("obmc-flash-bmc-setenv@fieldmode\\x3dtrue.service", 483 "replace"); 484 bus.call_noreply(method); 485 486 method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, 487 SYSTEMD_INTERFACE, "StopUnit"); 488 method.append("usr-local.mount", "replace"); 489 bus.call_noreply(method); 490 491 std::vector<std::string> usrLocal = {"usr-local.mount"}; 492 493 method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, 494 SYSTEMD_INTERFACE, "MaskUnitFiles"); 495 method.append(usrLocal, false, true); 496 bus.call_noreply(method); 497 } 498 else if (!value && control::FieldMode::fieldModeEnabled()) 499 { 500 elog<NotAllowed>(xyz::openbmc_project::Common::NotAllowed::REASON( 501 "FieldMode is not allowed to be cleared")); 502 } 503 504 return control::FieldMode::fieldModeEnabled(); 505 } 506 507 void ItemUpdater::restoreFieldModeStatus() 508 { 509 std::ifstream input("/dev/mtd/u-boot-env"); 510 std::string envVar; 511 std::getline(input, envVar); 512 513 if (envVar.find("fieldmode=true") != std::string::npos) 514 { 515 ItemUpdater::fieldModeEnabled(true); 516 } 517 } 518 519 void ItemUpdater::setBMCInventoryPath() 520 { 521 auto depth = 0; 522 auto mapperCall = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH, 523 MAPPER_INTERFACE, "GetSubTreePaths"); 524 525 mapperCall.append(INVENTORY_PATH); 526 mapperCall.append(depth); 527 std::vector<std::string> filter = {BMC_INVENTORY_INTERFACE}; 528 mapperCall.append(filter); 529 530 try 531 { 532 auto response = bus.call(mapperCall); 533 534 using ObjectPaths = std::vector<std::string>; 535 ObjectPaths result; 536 response.read(result); 537 538 if (!result.empty()) 539 { 540 bmcInventoryPath = result.front(); 541 } 542 } 543 catch (const sdbusplus::exception::SdBusError& e) 544 { 545 log<level::ERR>("Error in mapper GetSubTreePath"); 546 return; 547 } 548 549 return; 550 } 551 552 void ItemUpdater::createActiveAssociation(const std::string& path) 553 { 554 assocs.emplace_back( 555 std::make_tuple(ACTIVE_FWD_ASSOCIATION, ACTIVE_REV_ASSOCIATION, path)); 556 associations(assocs); 557 } 558 559 void ItemUpdater::createFunctionalAssociation(const std::string& path) 560 { 561 assocs.emplace_back(std::make_tuple(FUNCTIONAL_FWD_ASSOCIATION, 562 FUNCTIONAL_REV_ASSOCIATION, path)); 563 associations(assocs); 564 } 565 566 void ItemUpdater::createUpdateableAssociation(const std::string& path) 567 { 568 assocs.emplace_back(std::make_tuple(UPDATEABLE_FWD_ASSOCIATION, 569 UPDATEABLE_REV_ASSOCIATION, path)); 570 associations(assocs); 571 } 572 573 void ItemUpdater::removeAssociations(const std::string& path) 574 { 575 for (auto iter = assocs.begin(); iter != assocs.end();) 576 { 577 if ((std::get<2>(*iter)).compare(path) == 0) 578 { 579 iter = assocs.erase(iter); 580 associations(assocs); 581 } 582 else 583 { 584 ++iter; 585 } 586 } 587 } 588 589 bool ItemUpdater::isLowestPriority(uint8_t value) 590 { 591 for (const auto& intf : activations) 592 { 593 if (intf.second->redundancyPriority) 594 { 595 if (intf.second->redundancyPriority.get()->priority() < value) 596 { 597 return false; 598 } 599 } 600 } 601 return true; 602 } 603 604 void ItemUpdater::updateUbootEnvVars(const std::string& versionId) 605 { 606 helper.updateUbootVersionId(versionId); 607 } 608 609 void ItemUpdater::resetUbootEnvVars() 610 { 611 decltype(activations.begin()->second->redundancyPriority.get()->priority()) 612 lowestPriority = std::numeric_limits<uint8_t>::max(); 613 decltype(activations.begin()->second->versionId) lowestPriorityVersion; 614 for (const auto& intf : activations) 615 { 616 if (!intf.second->redundancyPriority.get()) 617 { 618 // Skip this version if the redundancyPriority is not initialized. 619 continue; 620 } 621 622 if (intf.second->redundancyPriority.get()->priority() <= lowestPriority) 623 { 624 lowestPriority = intf.second->redundancyPriority.get()->priority(); 625 lowestPriorityVersion = intf.second->versionId; 626 } 627 } 628 629 // Update the U-boot environment variable to point to the lowest priority 630 updateUbootEnvVars(lowestPriorityVersion); 631 } 632 633 void ItemUpdater::freeSpace(Activation& caller) 634 { 635 // Versions with the highest priority in front 636 std::priority_queue<std::pair<int, std::string>, 637 std::vector<std::pair<int, std::string>>, 638 std::less<std::pair<int, std::string>>> 639 versionsPQ; 640 641 std::size_t count = 0; 642 for (const auto& iter : activations) 643 { 644 if ((iter.second.get()->activation() == 645 server::Activation::Activations::Active) || 646 (iter.second.get()->activation() == 647 server::Activation::Activations::Failed)) 648 { 649 count++; 650 // Don't put the functional version on the queue since we can't 651 // remove the "running" BMC version. 652 // If ACTIVE_BMC_MAX_ALLOWED <= 1, there is only one active BMC, 653 // so remove functional version as well. 654 // Don't delete the the Activation object that called this function. 655 if ((versions.find(iter.second->versionId) 656 ->second->isFunctional() && 657 ACTIVE_BMC_MAX_ALLOWED > 1) || 658 (iter.second->versionId == caller.versionId)) 659 { 660 continue; 661 } 662 663 // Failed activations don't have priority, assign them a large value 664 // for sorting purposes. 665 auto priority = 999; 666 if (iter.second.get()->activation() == 667 server::Activation::Activations::Active) 668 { 669 priority = iter.second->redundancyPriority.get()->priority(); 670 } 671 672 versionsPQ.push(std::make_pair(priority, iter.second->versionId)); 673 } 674 } 675 676 // If the number of BMC versions is over ACTIVE_BMC_MAX_ALLOWED -1, 677 // remove the highest priority one(s). 678 while ((count >= ACTIVE_BMC_MAX_ALLOWED) && (!versionsPQ.empty())) 679 { 680 erase(versionsPQ.top().second); 681 versionsPQ.pop(); 682 count--; 683 } 684 } 685 686 void ItemUpdater::mirrorUbootToAlt() 687 { 688 helper.mirrorAlt(); 689 } 690 691 } // namespace updater 692 } // namespace software 693 } // namespace phosphor 694