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