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