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