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