1 #include "config.h" 2 3 #include "item_updater_ubi.hpp" 4 5 #include "activation_ubi.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 std::unique_ptr<Activation> ItemUpdaterUbi::createActivationObject( 39 const std::string& path, const std::string& versionId, 40 const std::string& extVersion, 41 sdbusplus::xyz::openbmc_project::Software::server::Activation::Activations 42 activationStatus, 43 AssociationList& assocs) 44 { 45 return std::make_unique<ActivationUbi>( 46 bus, path, *this, versionId, extVersion, activationStatus, assocs); 47 } 48 49 std::unique_ptr<Version> ItemUpdaterUbi::createVersionObject( 50 const std::string& objPath, const std::string& versionId, 51 const std::string& versionString, 52 sdbusplus::xyz::openbmc_project::Software::server::Version::VersionPurpose 53 versionPurpose, 54 const std::string& filePath) 55 { 56 auto version = std::make_unique<Version>( 57 bus, objPath, *this, versionId, versionString, versionPurpose, filePath, 58 std::bind(&ItemUpdaterUbi::erase, this, std::placeholders::_1)); 59 version->deleteObject = std::make_unique<Delete>(bus, objPath, *version); 60 return version; 61 } 62 63 bool ItemUpdaterUbi::validateImage(const std::string& path) 64 { 65 return validateSquashFSImage(path) == 0; 66 } 67 68 void ItemUpdaterUbi::processPNORImage() 69 { 70 // Read pnor.toc from folders under /media/ 71 // to get Active Software Versions. 72 for (const auto& iter : fs::directory_iterator(MEDIA_DIR)) 73 { 74 auto activationState = server::Activation::Activations::Active; 75 76 static const auto PNOR_RO_PREFIX_LEN = strlen(PNOR_RO_PREFIX); 77 static const auto PNOR_RW_PREFIX_LEN = strlen(PNOR_RW_PREFIX); 78 79 // Check if the PNOR_RO_PREFIX is the prefix of the iter.path 80 if (0 == 81 iter.path().native().compare(0, PNOR_RO_PREFIX_LEN, PNOR_RO_PREFIX)) 82 { 83 // The versionId is extracted from the path 84 // for example /media/pnor-ro-2a1022fe. 85 auto id = iter.path().native().substr(PNOR_RO_PREFIX_LEN); 86 auto pnorTOC = iter.path() / PNOR_TOC_FILE; 87 if (!fs::is_regular_file(pnorTOC)) 88 { 89 log<level::ERR>("Failed to read pnorTOC.", 90 entry("FILENAME=%s", pnorTOC.c_str())); 91 ItemUpdaterUbi::erase(id); 92 continue; 93 } 94 auto keyValues = Version::getValue( 95 pnorTOC, {{"version", ""}, {"extended_version", ""}}); 96 auto& version = keyValues.at("version"); 97 if (version.empty()) 98 { 99 log<level::ERR>("Failed to read version from pnorTOC", 100 entry("FILENAME=%s", pnorTOC.c_str())); 101 activationState = server::Activation::Activations::Invalid; 102 } 103 104 auto& extendedVersion = keyValues.at("extended_version"); 105 if (extendedVersion.empty()) 106 { 107 log<level::ERR>("Failed to read extendedVersion from pnorTOC", 108 entry("FILENAME=%s", pnorTOC.c_str())); 109 activationState = server::Activation::Activations::Invalid; 110 } 111 112 auto purpose = server::Version::VersionPurpose::Host; 113 auto path = fs::path(SOFTWARE_OBJPATH) / id; 114 AssociationList associations = {}; 115 116 if (activationState == server::Activation::Activations::Active) 117 { 118 // Create an association to the host inventory item 119 associations.emplace_back(std::make_tuple( 120 ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION, 121 HOST_INVENTORY_PATH)); 122 123 // Create an active association since this image is active 124 createActiveAssociation(path); 125 } 126 127 // Create Activation instance for this version. 128 activations.insert( 129 std::make_pair(id, std::make_unique<ActivationUbi>( 130 bus, path, *this, id, extendedVersion, 131 activationState, associations))); 132 133 // If Active, create RedundancyPriority instance for this version. 134 if (activationState == server::Activation::Activations::Active) 135 { 136 uint8_t priority = std::numeric_limits<uint8_t>::max(); 137 if (!restoreFromFile(id, priority)) 138 { 139 log<level::ERR>("Unable to restore priority from file.", 140 entry("VERSIONID=%s", id.c_str())); 141 } 142 activations.find(id)->second->redundancyPriority = 143 std::make_unique<RedundancyPriorityUbi>( 144 bus, path, *(activations.find(id)->second), priority); 145 } 146 147 // Create Version instance for this version. 148 auto versionPtr = std::make_unique<Version>( 149 bus, path, *this, id, version, purpose, "", 150 std::bind(&ItemUpdaterUbi::erase, this, std::placeholders::_1)); 151 versionPtr->deleteObject = 152 std::make_unique<Delete>(bus, path, *versionPtr); 153 versions.insert(std::make_pair(id, std::move(versionPtr))); 154 } 155 else if (0 == iter.path().native().compare(0, PNOR_RW_PREFIX_LEN, 156 PNOR_RW_PREFIX)) 157 { 158 auto id = iter.path().native().substr(PNOR_RW_PREFIX_LEN); 159 auto roDir = PNOR_RO_PREFIX + id; 160 if (!fs::is_directory(roDir)) 161 { 162 log<level::ERR>("No corresponding read-only volume found.", 163 entry("DIRNAME=%s", roDir.c_str())); 164 ItemUpdaterUbi::erase(id); 165 } 166 } 167 } 168 169 // Look at the RO symlink to determine if there is a functional image 170 auto id = determineId(PNOR_RO_ACTIVE_PATH); 171 if (!id.empty()) 172 { 173 updateFunctionalAssociation(id); 174 } 175 return; 176 } 177 178 int ItemUpdaterUbi::validateSquashFSImage(const std::string& filePath) 179 { 180 auto file = fs::path(filePath) / squashFSImage; 181 if (fs::is_regular_file(file)) 182 { 183 return 0; 184 } 185 else 186 { 187 log<level::ERR>("Failed to find the SquashFS image."); 188 return -1; 189 } 190 } 191 192 void ItemUpdaterUbi::removeReadOnlyPartition(std::string versionId) 193 { 194 auto serviceFile = "obmc-flash-bios-ubiumount-ro@" + versionId + ".service"; 195 196 // Remove the read-only partitions. 197 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, 198 SYSTEMD_INTERFACE, "StartUnit"); 199 method.append(serviceFile, "replace"); 200 bus.call_noreply(method); 201 } 202 203 void ItemUpdaterUbi::removeReadWritePartition(std::string versionId) 204 { 205 auto serviceFile = "obmc-flash-bios-ubiumount-rw@" + versionId + ".service"; 206 207 // Remove the read-write partitions. 208 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, 209 SYSTEMD_INTERFACE, "StartUnit"); 210 method.append(serviceFile, "replace"); 211 bus.call_noreply(method); 212 } 213 214 void ItemUpdaterUbi::reset() 215 { 216 std::vector<uint8_t> mboxdArgs; 217 218 // Suspend mboxd - no args required. 219 auto dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, 220 MBOXD_INTERFACE, "cmd"); 221 222 dbusCall.append(static_cast<uint8_t>(3), mboxdArgs); 223 224 auto responseMsg = bus.call(dbusCall); 225 if (responseMsg.is_method_error()) 226 { 227 log<level::ERR>("Error in mboxd suspend call"); 228 elog<InternalFailure>(); 229 } 230 231 constexpr static auto patchDir = "/usr/local/share/pnor"; 232 if (fs::is_directory(patchDir)) 233 { 234 for (const auto& iter : fs::directory_iterator(patchDir)) 235 { 236 fs::remove_all(iter); 237 } 238 } 239 240 // Clear the read-write partitions. 241 for (const auto& it : activations) 242 { 243 auto rwDir = PNOR_RW_PREFIX + it.first; 244 if (fs::is_directory(rwDir)) 245 { 246 for (const auto& iter : fs::directory_iterator(rwDir)) 247 { 248 fs::remove_all(iter); 249 } 250 } 251 } 252 253 // Clear the preserved partition. 254 if (fs::is_directory(PNOR_PRSV)) 255 { 256 for (const auto& iter : fs::directory_iterator(PNOR_PRSV)) 257 { 258 fs::remove_all(iter); 259 } 260 } 261 262 // Resume mboxd with arg 1, indicating that the flash was modified. 263 dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, MBOXD_INTERFACE, 264 "cmd"); 265 266 mboxdArgs.push_back(1); 267 dbusCall.append(static_cast<uint8_t>(4), mboxdArgs); 268 269 responseMsg = bus.call(dbusCall); 270 if (responseMsg.is_method_error()) 271 { 272 log<level::ERR>("Error in mboxd resume call"); 273 elog<InternalFailure>(); 274 } 275 276 return; 277 } 278 279 bool ItemUpdaterUbi::isVersionFunctional(const std::string& versionId) 280 { 281 if (!fs::exists(PNOR_RO_ACTIVE_PATH)) 282 { 283 return false; 284 } 285 286 fs::path activeRO = fs::read_symlink(PNOR_RO_ACTIVE_PATH); 287 288 if (!fs::is_directory(activeRO)) 289 { 290 return false; 291 } 292 293 if (activeRO.string().find(versionId) == std::string::npos) 294 { 295 return false; 296 } 297 298 // active PNOR is the version we're checking 299 return true; 300 } 301 302 void ItemUpdaterUbi::freePriority(uint8_t value, const std::string& versionId) 303 { 304 // TODO openbmc/openbmc#1896 Improve the performance of this function 305 for (const auto& intf : activations) 306 { 307 if (intf.second->redundancyPriority) 308 { 309 if (intf.second->redundancyPriority.get()->priority() == value && 310 intf.second->versionId != versionId) 311 { 312 intf.second->redundancyPriority.get()->priority(value + 1); 313 } 314 } 315 } 316 } 317 318 bool ItemUpdaterUbi::erase(std::string entryId) 319 { 320 if (!ItemUpdater::erase(entryId)) 321 { 322 return false; 323 } 324 325 // Remove priority persistence file 326 removeFile(entryId); 327 328 // Removing read-only and read-write partitions 329 removeReadWritePartition(entryId); 330 removeReadOnlyPartition(entryId); 331 332 return true; 333 } 334 335 void ItemUpdaterUbi::deleteAll() 336 { 337 auto chassisOn = isChassisOn(); 338 339 for (const auto& activationIt : activations) 340 { 341 if (isVersionFunctional(activationIt.first) && chassisOn) 342 { 343 continue; 344 } 345 else 346 { 347 ItemUpdaterUbi::erase(activationIt.first); 348 } 349 } 350 351 // Remove any remaining pnor-ro- or pnor-rw- volumes that do not match 352 // the current version. 353 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, 354 SYSTEMD_INTERFACE, "StartUnit"); 355 method.append("obmc-flash-bios-cleanup.service", "replace"); 356 bus.call_noreply(method); 357 } 358 359 // TODO: openbmc/openbmc#1402 Monitor flash usage 360 bool ItemUpdaterUbi::freeSpace() 361 { 362 bool isSpaceFreed = false; 363 // Versions with the highest priority in front 364 std::priority_queue<std::pair<int, std::string>, 365 std::vector<std::pair<int, std::string>>, 366 std::less<std::pair<int, std::string>>> 367 versionsPQ; 368 369 std::size_t count = 0; 370 for (const auto& iter : activations) 371 { 372 if (iter.second.get()->activation() == 373 server::Activation::Activations::Active) 374 { 375 count++; 376 // Don't put the functional version on the queue since we can't 377 // remove the "running" PNOR version if it allows multiple PNORs 378 // But removing functional version if there is only one PNOR. 379 if (ACTIVE_PNOR_MAX_ALLOWED > 1 && 380 isVersionFunctional(iter.second->versionId)) 381 { 382 continue; 383 } 384 versionsPQ.push(std::make_pair( 385 iter.second->redundancyPriority.get()->priority(), 386 iter.second->versionId)); 387 } 388 } 389 390 // If the number of PNOR versions is over ACTIVE_PNOR_MAX_ALLOWED -1, 391 // remove the highest priority one(s). 392 while ((count >= ACTIVE_PNOR_MAX_ALLOWED) && (!versionsPQ.empty())) 393 { 394 erase(versionsPQ.top().second); 395 versionsPQ.pop(); 396 count--; 397 isSpaceFreed = true; 398 } 399 return isSpaceFreed; 400 } 401 402 std::string ItemUpdaterUbi::determineId(const std::string& symlinkPath) 403 { 404 if (!fs::exists(symlinkPath)) 405 { 406 return {}; 407 } 408 409 auto target = fs::canonical(symlinkPath).string(); 410 411 // check to make sure the target really exists 412 if (!fs::is_regular_file(target + "/" + PNOR_TOC_FILE)) 413 { 414 return {}; 415 } 416 // Get the image <id> from the symlink target 417 // for example /media/ro-2a1022fe 418 static const auto PNOR_RO_PREFIX_LEN = strlen(PNOR_RO_PREFIX); 419 return target.substr(PNOR_RO_PREFIX_LEN); 420 } 421 422 void GardResetUbi::reset() 423 { 424 // The GARD partition is currently misspelled "GUARD." This file path will 425 // need to be updated in the future. 426 auto path = fs::path(PNOR_PRSV_ACTIVE_PATH); 427 path /= "GUARD"; 428 std::vector<uint8_t> mboxdArgs; 429 430 auto dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, 431 MBOXD_INTERFACE, "cmd"); 432 433 // Suspend mboxd - no args required. 434 dbusCall.append(static_cast<uint8_t>(3), mboxdArgs); 435 436 auto responseMsg = bus.call(dbusCall); 437 if (responseMsg.is_method_error()) 438 { 439 log<level::ERR>("Error in mboxd suspend call"); 440 elog<InternalFailure>(); 441 } 442 443 if (fs::is_regular_file(path)) 444 { 445 fs::remove(path); 446 } 447 448 dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, MBOXD_INTERFACE, 449 "cmd"); 450 451 // Resume mboxd with arg 1, indicating that the flash is modified. 452 mboxdArgs.push_back(1); 453 dbusCall.append(static_cast<uint8_t>(4), mboxdArgs); 454 455 responseMsg = bus.call(dbusCall); 456 if (responseMsg.is_method_error()) 457 { 458 log<level::ERR>("Error in mboxd resume call"); 459 elog<InternalFailure>(); 460 } 461 } 462 463 } // namespace updater 464 } // namespace software 465 } // namespace openpower 466