1 #include <string> 2 #include <experimental/filesystem> 3 #include <fstream> 4 #include <phosphor-logging/elog-errors.hpp> 5 #include <phosphor-logging/log.hpp> 6 #include "xyz/openbmc_project/Common/error.hpp" 7 #include <xyz/openbmc_project/Software/Version/server.hpp> 8 #include "version.hpp" 9 #include "config.h" 10 #include "item_updater.hpp" 11 #include "activation.hpp" 12 #include "serialize.hpp" 13 14 namespace openpower 15 { 16 namespace software 17 { 18 namespace updater 19 { 20 21 // When you see server:: you know we're referencing our base class 22 namespace server = sdbusplus::xyz::openbmc_project::Software::server; 23 namespace fs = std::experimental::filesystem; 24 25 using namespace sdbusplus::xyz::openbmc_project::Common::Error; 26 using namespace phosphor::logging; 27 28 constexpr auto squashFSImage = "pnor.xz.squashfs"; 29 30 // TODO: Change paths once openbmc/openbmc#1663 is completed. 31 constexpr auto MBOXD_INTERFACE = "org.openbmc.mboxd"; 32 constexpr auto MBOXD_PATH = "/org/openbmc/mboxd"; 33 34 void ItemUpdater::createActivation(sdbusplus::message::message& m) 35 { 36 using SVersion = server::Version; 37 using VersionPurpose = SVersion::VersionPurpose; 38 namespace msg = sdbusplus::message; 39 namespace variant_ns = msg::variant_ns; 40 41 sdbusplus::message::object_path objPath; 42 std::map<std::string, 43 std::map<std::string, msg::variant<std::string>>> interfaces; 44 m.read(objPath, interfaces); 45 46 std::string path(std::move(objPath)); 47 std::string filePath; 48 auto purpose = VersionPurpose::Unknown; 49 std::string version; 50 51 for (const auto& intf : interfaces) 52 { 53 if (intf.first == VERSION_IFACE) 54 { 55 for (const auto& property : intf.second) 56 { 57 if (property.first == "Purpose") 58 { 59 // Only process the Host and System images 60 auto value = SVersion::convertVersionPurposeFromString( 61 variant_ns::get<std::string>(property.second)); 62 63 if (value == VersionPurpose::Host || 64 value == VersionPurpose::System) 65 { 66 purpose = value; 67 } 68 } 69 else if (property.first == "Version") 70 { 71 version = variant_ns::get<std::string>(property.second); 72 } 73 } 74 } 75 else if (intf.first == FILEPATH_IFACE) 76 { 77 for (const auto& property : intf.second) 78 { 79 if (property.first == "Path") 80 { 81 filePath = variant_ns::get<std::string>(property.second); 82 } 83 } 84 } 85 } 86 if ((filePath.empty()) || (purpose == VersionPurpose::Unknown)) 87 { 88 return; 89 } 90 91 // Version id is the last item in the path 92 auto pos = path.rfind("/"); 93 if (pos == std::string::npos) 94 { 95 log<level::ERR>("No version id found in object path", 96 entry("OBJPATH=%s", path)); 97 return; 98 } 99 100 auto versionId = path.substr(pos + 1); 101 102 if (activations.find(versionId) == activations.end()) 103 { 104 // Determine the Activation state by processing the given image dir. 105 auto activationState = server::Activation::Activations::Invalid; 106 AssociationList associations = {}; 107 if (ItemUpdater::validateSquashFSImage(filePath) == 0) 108 { 109 activationState = server::Activation::Activations::Ready; 110 // Create an association to the host inventory item 111 associations.emplace_back(std::make_tuple( 112 ACTIVATION_FWD_ASSOCIATION, 113 ACTIVATION_REV_ASSOCIATION, 114 HOST_INVENTORY_PATH)); 115 } 116 117 fs::path manifestPath(filePath); 118 manifestPath /= MANIFEST_FILE; 119 std::string extendedVersion = (Version::getValue(manifestPath.string(), 120 std::map<std::string, std::string> 121 {{"extended_version", ""}})).begin()->second; 122 123 activations.insert(std::make_pair( 124 versionId, 125 std::make_unique<Activation>( 126 bus, 127 path, 128 *this, 129 versionId, 130 extendedVersion, 131 activationState, 132 associations))); 133 134 auto versionPtr = std::make_unique<Version>( 135 bus, 136 path, 137 *this, 138 versionId, 139 version, 140 purpose, 141 filePath, 142 std::bind(&ItemUpdater::erase, 143 this, 144 std::placeholders::_1)); 145 versionPtr->deleteObject = 146 std::make_unique<Delete>(bus, path, *versionPtr); 147 versions.insert(std::make_pair(versionId, std::move(versionPtr))); 148 } 149 return; 150 } 151 152 void ItemUpdater::processPNORImage() 153 { 154 // Read pnor.toc from folders under /media/ 155 // to get Active Software Versions. 156 for (const auto& iter : fs::directory_iterator(MEDIA_DIR)) 157 { 158 auto activationState = server::Activation::Activations::Active; 159 160 static const auto PNOR_RO_PREFIX_LEN = strlen(PNOR_RO_PREFIX); 161 static const auto PNOR_RW_PREFIX_LEN = strlen(PNOR_RW_PREFIX); 162 163 // Check if the PNOR_RO_PREFIX is the prefix of the iter.path 164 if (0 == iter.path().native().compare(0, PNOR_RO_PREFIX_LEN, 165 PNOR_RO_PREFIX)) 166 { 167 // The versionId is extracted from the path 168 // for example /media/pnor-ro-2a1022fe. 169 auto id = iter.path().native().substr(PNOR_RO_PREFIX_LEN); 170 auto pnorTOC = iter.path() / PNOR_TOC_FILE; 171 if (!fs::is_regular_file(pnorTOC)) 172 { 173 log<level::ERR>("Failed to read pnorTOC.", 174 entry("FILENAME=%s", pnorTOC.string())); 175 ItemUpdater::erase(id); 176 continue; 177 } 178 auto keyValues = 179 Version::getValue(pnorTOC, 180 {{ "version", "" }, 181 { "extended_version", "" } }); 182 auto& version = keyValues.at("version"); 183 if (version.empty()) 184 { 185 log<level::ERR>("Failed to read version from pnorTOC", 186 entry("FILENAME=%s", pnorTOC.string())); 187 activationState = server::Activation::Activations::Invalid; 188 } 189 190 auto& extendedVersion = keyValues.at("extended_version"); 191 if (extendedVersion.empty()) 192 { 193 log<level::ERR>("Failed to read extendedVersion from pnorTOC", 194 entry("FILENAME=%s", pnorTOC.string())); 195 activationState = server::Activation::Activations::Invalid; 196 } 197 198 auto purpose = server::Version::VersionPurpose::Host; 199 auto path = fs::path(SOFTWARE_OBJPATH) / id; 200 AssociationList associations = {}; 201 202 if (activationState == server::Activation::Activations::Active) 203 { 204 // Create an association to the host inventory item 205 associations.emplace_back(std::make_tuple( 206 ACTIVATION_FWD_ASSOCIATION, 207 ACTIVATION_REV_ASSOCIATION, 208 HOST_INVENTORY_PATH)); 209 210 // Create an active association since this image is active 211 createActiveAssociation(path); 212 } 213 214 // Create Activation instance for this version. 215 activations.insert(std::make_pair( 216 id, 217 std::make_unique<Activation>( 218 bus, 219 path, 220 *this, 221 id, 222 extendedVersion, 223 activationState, 224 associations))); 225 226 // If Active, create RedundancyPriority instance for this version. 227 if (activationState == server::Activation::Activations::Active) 228 { 229 uint8_t priority = std::numeric_limits<uint8_t>::max(); 230 if (!restoreFromFile(id, priority)) 231 { 232 log<level::ERR>("Unable to restore priority from file.", 233 entry("VERSIONID=%s", id)); 234 } 235 activations.find(id)->second->redundancyPriority = 236 std::make_unique<RedundancyPriority>( 237 bus, 238 path, 239 *(activations.find(id)->second), 240 priority); 241 } 242 243 // Create Version instance for this version. 244 auto versionPtr = std::make_unique<Version>( 245 bus, 246 path, 247 *this, 248 id, 249 version, 250 purpose, 251 "", 252 std::bind(&ItemUpdater::erase, 253 this, 254 std::placeholders::_1)); 255 versionPtr->deleteObject = 256 std::make_unique<Delete>(bus, path, *versionPtr); 257 versions.insert(std::make_pair( 258 id, 259 std::move(versionPtr))); 260 } 261 else if (0 == iter.path().native().compare(0, PNOR_RW_PREFIX_LEN, 262 PNOR_RW_PREFIX)) 263 { 264 auto id = iter.path().native().substr(PNOR_RW_PREFIX_LEN); 265 auto roDir = PNOR_RO_PREFIX + id; 266 if (!fs::is_directory(roDir)) 267 { 268 log<level::ERR>("No corresponding read-only volume found.", 269 entry("DIRNAME=%s", roDir)); 270 ItemUpdater::erase(id); 271 } 272 } 273 } 274 275 // Look at the RO symlink to determine if there is a functional image 276 auto id = determineId(PNOR_RO_ACTIVE_PATH); 277 if (!id.empty()) 278 { 279 updateFunctionalAssociation(std::string{SOFTWARE_OBJPATH} + '/' + id); 280 } 281 return; 282 } 283 284 int ItemUpdater::validateSquashFSImage(const std::string& filePath) 285 { 286 auto file = fs::path(filePath) / squashFSImage; 287 if (fs::is_regular_file(file)) 288 { 289 return 0; 290 } 291 else 292 { 293 log<level::ERR>("Failed to find the SquashFS image."); 294 return -1; 295 } 296 } 297 298 void ItemUpdater::removeReadOnlyPartition(std::string versionId) 299 { 300 auto serviceFile = "obmc-flash-bios-ubiumount-ro@" + versionId + 301 ".service"; 302 303 // Remove the read-only partitions. 304 auto method = bus.new_method_call( 305 SYSTEMD_BUSNAME, 306 SYSTEMD_PATH, 307 SYSTEMD_INTERFACE, 308 "StartUnit"); 309 method.append(serviceFile, "replace"); 310 bus.call_noreply(method); 311 } 312 313 void ItemUpdater::removeReadWritePartition(std::string versionId) 314 { 315 auto serviceFile = "obmc-flash-bios-ubiumount-rw@" + versionId + 316 ".service"; 317 318 // Remove the read-write partitions. 319 auto method = bus.new_method_call( 320 SYSTEMD_BUSNAME, 321 SYSTEMD_PATH, 322 SYSTEMD_INTERFACE, 323 "StartUnit"); 324 method.append(serviceFile, "replace"); 325 bus.call_noreply(method); 326 } 327 328 void ItemUpdater::reset() 329 { 330 constexpr static auto patchDir = "/usr/local/share/pnor"; 331 if (fs::is_directory(patchDir)) 332 { 333 for (const auto& iter : fs::directory_iterator(patchDir)) 334 { 335 fs::remove_all(iter); 336 } 337 } 338 339 for (const auto& it : activations) 340 { 341 auto serviceFile = "obmc-flash-bios-ubiclear@pnor-rw-" + it.first + 342 ".service"; 343 344 // Clear the read-write partitions. 345 auto method = bus.new_method_call( 346 SYSTEMD_BUSNAME, 347 SYSTEMD_PATH, 348 SYSTEMD_INTERFACE, 349 "StartUnit"); 350 method.append(serviceFile, "replace"); 351 auto reply = bus.call(method); 352 353 if (reply.is_method_error()) 354 { 355 log<level::ERR>("Failed to clear read-write partitions", 356 entry("SERVICE_FILE=%s", serviceFile)); 357 elog<InternalFailure>(); 358 } 359 360 removeFile(it.first); 361 } 362 static constexpr auto serviceFile = 363 "obmc-flash-bios-ubiclear@pnor-prsv.service"; 364 // Clear the preserved partition. 365 auto method = bus.new_method_call( 366 SYSTEMD_BUSNAME, 367 SYSTEMD_PATH, 368 SYSTEMD_INTERFACE, 369 "StartUnit"); 370 method.append(serviceFile, "replace"); 371 auto reply = bus.call(method); 372 373 if (reply.is_method_error()) 374 { 375 log<level::ERR>("Failed to clear preserved partition", 376 entry("SERVICE_FILE=%s", serviceFile)); 377 elog<InternalFailure>(); 378 } 379 380 return; 381 } 382 383 bool ItemUpdater::isVersionFunctional(const std::string& versionId) 384 { 385 if (!fs::exists(PNOR_RO_ACTIVE_PATH)) 386 { 387 return false; 388 } 389 390 fs::path activeRO = fs::read_symlink(PNOR_RO_ACTIVE_PATH); 391 392 if (!fs::is_directory(activeRO)) 393 { 394 return false; 395 } 396 397 if (activeRO.string().find(versionId) == std::string::npos) 398 { 399 return false; 400 } 401 402 // active PNOR is the version we're checking 403 return true; 404 } 405 406 bool ItemUpdater::isChassisOn() 407 { 408 auto mapperCall = bus.new_method_call( 409 MAPPER_BUSNAME, 410 MAPPER_PATH, 411 MAPPER_INTERFACE, 412 "GetObject"); 413 414 mapperCall.append(CHASSIS_STATE_PATH, 415 std::vector<std::string>({CHASSIS_STATE_OBJ})); 416 auto mapperResponseMsg = bus.call(mapperCall); 417 if (mapperResponseMsg.is_method_error()) 418 { 419 log<level::ERR>("Error in Mapper call"); 420 elog<InternalFailure>(); 421 } 422 using MapperResponseType = std::map<std::string, std::vector<std::string>>; 423 MapperResponseType mapperResponse; 424 mapperResponseMsg.read(mapperResponse); 425 if (mapperResponse.empty()) 426 { 427 log<level::ERR>("Invalid Response from mapper"); 428 elog<InternalFailure>(); 429 } 430 431 auto method = bus.new_method_call((mapperResponse.begin()->first).c_str(), 432 CHASSIS_STATE_PATH, 433 SYSTEMD_PROPERTY_INTERFACE, 434 "Get"); 435 method.append(CHASSIS_STATE_OBJ, "CurrentPowerState"); 436 auto response = bus.call(method); 437 if (response.is_method_error()) 438 { 439 log<level::ERR>("Error in fetching current Chassis State", 440 entry("MAPPERRESPONSE=%s", 441 (mapperResponse.begin()->first).c_str())); 442 elog<InternalFailure>(); 443 } 444 sdbusplus::message::variant<std::string> currentChassisState; 445 response.read(currentChassisState); 446 auto strParam = 447 sdbusplus::message::variant_ns::get<std::string>(currentChassisState); 448 return (strParam != CHASSIS_STATE_OFF); 449 } 450 451 void ItemUpdater::freePriority(uint8_t value, const std::string& versionId) 452 { 453 //TODO openbmc/openbmc#1896 Improve the performance of this function 454 for (const auto& intf : activations) 455 { 456 if (intf.second->redundancyPriority) 457 { 458 if (intf.second->redundancyPriority.get()->priority() == value && 459 intf.second->versionId != versionId) 460 { 461 intf.second->redundancyPriority.get()->priority(value + 1); 462 } 463 } 464 } 465 } 466 467 bool ItemUpdater::isLowestPriority(uint8_t value) 468 { 469 for (const auto& intf : activations) 470 { 471 if (intf.second->redundancyPriority) 472 { 473 if (intf.second->redundancyPriority.get()->priority() < value) 474 { 475 return false; 476 } 477 } 478 } 479 return true; 480 } 481 482 void ItemUpdater::erase(std::string entryId) 483 { 484 if (isVersionFunctional(entryId) && isChassisOn()) { 485 log<level::ERR>(("Error: Version " + entryId + \ 486 " is currently active and running on the host." \ 487 " Unable to remove.").c_str()); 488 return; 489 } 490 // Remove priority persistence file 491 removeFile(entryId); 492 493 // Removing read-only and read-write partitions 494 removeReadWritePartition(entryId); 495 removeReadOnlyPartition(entryId); 496 497 // Removing entry in versions map 498 auto it = versions.find(entryId); 499 if (it == versions.end()) 500 { 501 log<level::ERR>(("Error: Failed to find version " + entryId + \ 502 " in item updater versions map." \ 503 " Unable to remove.").c_str()); 504 } 505 else 506 { 507 versions.erase(entryId); 508 } 509 510 // Removing entry in activations map 511 auto ita = activations.find(entryId); 512 if (ita == activations.end()) 513 { 514 log<level::ERR>(("Error: Failed to find version " + entryId + \ 515 " in item updater activations map." \ 516 " Unable to remove.").c_str()); 517 } 518 else 519 { 520 activations.erase(entryId); 521 } 522 return; 523 } 524 525 void ItemUpdater::deleteAll() 526 { 527 for (const auto& activationIt : activations) 528 { 529 if (!isVersionFunctional(activationIt.first)) 530 { 531 ItemUpdater::erase(activationIt.first); 532 } 533 } 534 535 // Remove any remaining pnor-ro- or pnor-rw- volumes that do not match 536 // the current version. 537 auto method = bus.new_method_call( 538 SYSTEMD_BUSNAME, 539 SYSTEMD_PATH, 540 SYSTEMD_INTERFACE, 541 "StartUnit"); 542 method.append("obmc-flash-bios-cleanup.service", "replace"); 543 bus.call_noreply(method); 544 } 545 546 // TODO: openbmc/openbmc#1402 Monitor flash usage 547 void ItemUpdater::freeSpace() 548 { 549 std::size_t count = 0; 550 decltype(activations.begin()->second->redundancyPriority.get()->priority()) 551 highestPriority = 0; 552 decltype(activations.begin()->second->versionId) highestPriorityVersion; 553 for (const auto& iter : activations) 554 { 555 if (iter.second.get()->activation() == server::Activation::Activations::Active) 556 { 557 count++; 558 if (isVersionFunctional(iter.second->versionId)) 559 { 560 continue; 561 } 562 if (iter.second->redundancyPriority.get()->priority() >= highestPriority) 563 { 564 highestPriority = iter.second->redundancyPriority.get()->priority(); 565 highestPriorityVersion = iter.second->versionId; 566 } 567 } 568 } 569 // Remove the pnor version with highest priority since the PNOR 570 // can't hold more than 2 versions. 571 if (count >= ACTIVE_PNOR_MAX_ALLOWED) 572 { 573 erase(highestPriorityVersion); 574 } 575 } 576 577 void ItemUpdater::createActiveAssociation(const std::string& path) 578 { 579 assocs.emplace_back(std::make_tuple(ACTIVE_FWD_ASSOCIATION, 580 ACTIVE_REV_ASSOCIATION, 581 path)); 582 associations(assocs); 583 } 584 585 void ItemUpdater::updateFunctionalAssociation(const std::string& path) 586 { 587 // remove all functional associations 588 for (auto iter = assocs.begin(); iter != assocs.end();) 589 { 590 if ((std::get<0>(*iter)).compare(FUNCTIONAL_FWD_ASSOCIATION) == 0) 591 { 592 iter = assocs.erase(iter); 593 } 594 else 595 { 596 ++iter; 597 } 598 } 599 assocs.emplace_back(std::make_tuple(FUNCTIONAL_FWD_ASSOCIATION, 600 FUNCTIONAL_REV_ASSOCIATION, 601 path)); 602 associations(assocs); 603 } 604 605 void ItemUpdater::removeActiveAssociation(const std::string& path) 606 { 607 for (auto iter = assocs.begin(); iter != assocs.end();) 608 { 609 if ((std::get<0>(*iter)).compare(ACTIVE_FWD_ASSOCIATION) == 0 && 610 (std::get<2>(*iter)).compare(path) == 0) 611 { 612 iter = assocs.erase(iter); 613 associations(assocs); 614 } 615 else 616 { 617 ++iter; 618 } 619 } 620 } 621 622 std::string ItemUpdater::determineId(const std::string& symlinkPath) 623 { 624 if (!fs::exists(symlinkPath)) 625 { 626 return {}; 627 } 628 629 auto target = fs::canonical(symlinkPath).string(); 630 631 // check to make sure the target really exists 632 if (!fs::is_regular_file(target + "/" + PNOR_TOC_FILE)) 633 { 634 return {}; 635 } 636 // Get the image <id> from the symlink target 637 // for example /media/ro-2a1022fe 638 static const auto PNOR_RO_PREFIX_LEN = strlen(PNOR_RO_PREFIX); 639 return target.substr(PNOR_RO_PREFIX_LEN); 640 } 641 642 void GardReset::reset() 643 { 644 // The GARD partition is currently misspelled "GUARD." This file path will 645 // need to be updated in the future. 646 auto path = fs::path(PNOR_PRSV_ACTIVE_PATH); 647 path /= "GUARD"; 648 std::vector<uint8_t> mboxdArgs; 649 650 auto dbusCall = bus.new_method_call( 651 MBOXD_INTERFACE, 652 MBOXD_PATH, 653 MBOXD_INTERFACE, 654 "cmd"); 655 656 // Suspend mboxd - no args required. 657 dbusCall.append(static_cast<uint8_t>(3), mboxdArgs); 658 659 auto responseMsg = bus.call(dbusCall); 660 if (responseMsg.is_method_error()) 661 { 662 log<level::ERR>("Error in mboxd suspend call"); 663 elog<InternalFailure>(); 664 } 665 666 if (fs::is_regular_file(path)) 667 { 668 fs::remove(path); 669 } 670 671 dbusCall = bus.new_method_call( 672 MBOXD_INTERFACE, 673 MBOXD_PATH, 674 MBOXD_INTERFACE, 675 "cmd"); 676 677 // Resume mboxd with arg 1, indicating that the flash is modified. 678 mboxdArgs.push_back(1); 679 dbusCall.append(static_cast<uint8_t>(4), mboxdArgs); 680 681 responseMsg = bus.call(dbusCall); 682 if (responseMsg.is_method_error()) 683 { 684 log<level::ERR>("Error in mboxd resume call"); 685 elog<InternalFailure>(); 686 } 687 } 688 689 } // namespace updater 690 } // namespace software 691 } // namespace openpower 692