1 #include "config.h" 2 3 #include "item_updater_ubi.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 ItemUpdaterUbi::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 (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(&ItemUpdaterUbi::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 ItemUpdaterUbi::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 ItemUpdaterUbi::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(&ItemUpdaterUbi::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 ItemUpdaterUbi::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(id); 250 } 251 return; 252 } 253 254 int ItemUpdaterUbi::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 ItemUpdaterUbi::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 ItemUpdaterUbi::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 ItemUpdaterUbi::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 ItemUpdaterUbi::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 void ItemUpdaterUbi::freePriority(uint8_t value, const std::string& versionId) 379 { 380 // TODO openbmc/openbmc#1896 Improve the performance of this function 381 for (const auto& intf : activations) 382 { 383 if (intf.second->redundancyPriority) 384 { 385 if (intf.second->redundancyPriority.get()->priority() == value && 386 intf.second->versionId != versionId) 387 { 388 intf.second->redundancyPriority.get()->priority(value + 1); 389 } 390 } 391 } 392 } 393 394 bool ItemUpdaterUbi::isLowestPriority(uint8_t value) 395 { 396 for (const auto& intf : activations) 397 { 398 if (intf.second->redundancyPriority) 399 { 400 if (intf.second->redundancyPriority.get()->priority() < value) 401 { 402 return false; 403 } 404 } 405 } 406 return true; 407 } 408 409 bool ItemUpdaterUbi::erase(std::string entryId) 410 { 411 if (!ItemUpdater::erase(entryId)) 412 { 413 return false; 414 } 415 416 // Remove priority persistence file 417 removeFile(entryId); 418 419 // Removing read-only and read-write partitions 420 removeReadWritePartition(entryId); 421 removeReadOnlyPartition(entryId); 422 423 return true; 424 } 425 426 void ItemUpdaterUbi::deleteAll() 427 { 428 auto chassisOn = isChassisOn(); 429 430 for (const auto& activationIt : activations) 431 { 432 if (isVersionFunctional(activationIt.first) && chassisOn) 433 { 434 continue; 435 } 436 else 437 { 438 ItemUpdaterUbi::erase(activationIt.first); 439 } 440 } 441 442 // Remove any remaining pnor-ro- or pnor-rw- volumes that do not match 443 // the current version. 444 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, 445 SYSTEMD_INTERFACE, "StartUnit"); 446 method.append("obmc-flash-bios-cleanup.service", "replace"); 447 bus.call_noreply(method); 448 } 449 450 // TODO: openbmc/openbmc#1402 Monitor flash usage 451 void ItemUpdaterUbi::freeSpace() 452 { 453 // Versions with the highest priority in front 454 std::priority_queue<std::pair<int, std::string>, 455 std::vector<std::pair<int, std::string>>, 456 std::less<std::pair<int, std::string>>> 457 versionsPQ; 458 459 std::size_t count = 0; 460 for (const auto& iter : activations) 461 { 462 if (iter.second.get()->activation() == 463 server::Activation::Activations::Active) 464 { 465 count++; 466 // Don't put the functional version on the queue since we can't 467 // remove the "running" PNOR version if it allows multiple PNORs 468 // But removing functional version if there is only one PNOR. 469 if (ACTIVE_PNOR_MAX_ALLOWED > 1 && 470 isVersionFunctional(iter.second->versionId)) 471 { 472 continue; 473 } 474 versionsPQ.push(std::make_pair( 475 iter.second->redundancyPriority.get()->priority(), 476 iter.second->versionId)); 477 } 478 } 479 480 // If the number of PNOR versions is over ACTIVE_PNOR_MAX_ALLOWED -1, 481 // remove the highest priority one(s). 482 while ((count >= ACTIVE_PNOR_MAX_ALLOWED) && (!versionsPQ.empty())) 483 { 484 erase(versionsPQ.top().second); 485 versionsPQ.pop(); 486 count--; 487 } 488 } 489 490 std::string ItemUpdater::determineId(const std::string& symlinkPath) 491 { 492 if (!fs::exists(symlinkPath)) 493 { 494 return {}; 495 } 496 497 auto target = fs::canonical(symlinkPath).string(); 498 499 // check to make sure the target really exists 500 if (!fs::is_regular_file(target + "/" + PNOR_TOC_FILE)) 501 { 502 return {}; 503 } 504 // Get the image <id> from the symlink target 505 // for example /media/ro-2a1022fe 506 static const auto PNOR_RO_PREFIX_LEN = strlen(PNOR_RO_PREFIX); 507 return target.substr(PNOR_RO_PREFIX_LEN); 508 } 509 510 void GardReset::reset() 511 { 512 // The GARD partition is currently misspelled "GUARD." This file path will 513 // need to be updated in the future. 514 auto path = fs::path(PNOR_PRSV_ACTIVE_PATH); 515 path /= "GUARD"; 516 std::vector<uint8_t> mboxdArgs; 517 518 auto dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, 519 MBOXD_INTERFACE, "cmd"); 520 521 // Suspend mboxd - no args required. 522 dbusCall.append(static_cast<uint8_t>(3), mboxdArgs); 523 524 auto responseMsg = bus.call(dbusCall); 525 if (responseMsg.is_method_error()) 526 { 527 log<level::ERR>("Error in mboxd suspend call"); 528 elog<InternalFailure>(); 529 } 530 531 if (fs::is_regular_file(path)) 532 { 533 fs::remove(path); 534 } 535 536 dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, MBOXD_INTERFACE, 537 "cmd"); 538 539 // Resume mboxd with arg 1, indicating that the flash is modified. 540 mboxdArgs.push_back(1); 541 dbusCall.append(static_cast<uint8_t>(4), mboxdArgs); 542 543 responseMsg = bus.call(dbusCall); 544 if (responseMsg.is_method_error()) 545 { 546 log<level::ERR>("Error in mboxd resume call"); 547 elog<InternalFailure>(); 548 } 549 } 550 551 } // namespace updater 552 } // namespace software 553 } // namespace openpower 554