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. 240 if (std::filesystem::is_directory(PNOR_PRSV)) 241 { 242 for (const auto& iter : std::filesystem::directory_iterator(PNOR_PRSV)) 243 { 244 std::filesystem::remove_all(iter); 245 } 246 } 247 248 utils::hiomapdResume(bus); 249 } 250 251 bool ItemUpdaterUbi::isVersionFunctional(const std::string& versionId) 252 { 253 if (!std::filesystem::exists(PNOR_RO_ACTIVE_PATH)) 254 { 255 return false; 256 } 257 258 std::filesystem::path activeRO = 259 std::filesystem::read_symlink(PNOR_RO_ACTIVE_PATH); 260 261 if (!std::filesystem::is_directory(activeRO)) 262 { 263 return false; 264 } 265 266 if (activeRO.string().find(versionId) == std::string::npos) 267 { 268 return false; 269 } 270 271 // active PNOR is the version we're checking 272 return true; 273 } 274 275 void ItemUpdaterUbi::freePriority(uint8_t value, const std::string& versionId) 276 { 277 // Versions with the lowest priority in front 278 std::priority_queue<std::pair<int, std::string>, 279 std::vector<std::pair<int, std::string>>, 280 std::greater<std::pair<int, std::string>>> 281 versionsPQ; 282 283 for (const auto& intf : activations) 284 { 285 if (intf.second->redundancyPriority) 286 { 287 versionsPQ.push(std::make_pair( 288 intf.second->redundancyPriority.get()->priority(), 289 intf.second->versionId)); 290 } 291 } 292 293 while (!versionsPQ.empty()) 294 { 295 if (versionsPQ.top().first == value && 296 versionsPQ.top().second != versionId) 297 { 298 // Increase priority by 1 and update its value 299 ++value; 300 storeToFile(versionsPQ.top().second, value); 301 auto it = activations.find(versionsPQ.top().second); 302 it->second->redundancyPriority.get()->sdbusplus::xyz:: 303 openbmc_project::Software::server::RedundancyPriority::priority( 304 value); 305 } 306 versionsPQ.pop(); 307 } 308 } 309 310 bool ItemUpdaterUbi::erase(std::string entryId) 311 { 312 if (!ItemUpdater::erase(entryId)) 313 { 314 return false; 315 } 316 317 // Remove priority persistence file 318 removeFile(entryId); 319 320 // Removing read-only and read-write partitions 321 removeReadWritePartition(entryId); 322 removeReadOnlyPartition(entryId); 323 324 return true; 325 } 326 327 void ItemUpdaterUbi::deleteAll() 328 { 329 auto chassisOn = isChassisOn(); 330 331 for (const auto& activationIt : activations) 332 { 333 if (isVersionFunctional(activationIt.first) && chassisOn) 334 { 335 continue; 336 } 337 else 338 { 339 ItemUpdaterUbi::erase(activationIt.first); 340 } 341 } 342 343 // Remove any remaining pnor-ro- or pnor-rw- volumes that do not match 344 // the current version. 345 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, 346 SYSTEMD_INTERFACE, "StartUnit"); 347 method.append("obmc-flash-bios-cleanup.service", "replace"); 348 bus.call_noreply(method); 349 } 350 351 // TODO: openbmc/openbmc#1402 Monitor flash usage 352 bool ItemUpdaterUbi::freeSpace() 353 { 354 bool isSpaceFreed = false; 355 // Versions with the highest priority in front 356 std::priority_queue<std::pair<int, std::string>, 357 std::vector<std::pair<int, std::string>>, 358 std::less<std::pair<int, std::string>>> 359 versionsPQ; 360 361 std::size_t count = 0; 362 for (const auto& iter : activations) 363 { 364 if (iter.second.get()->activation() == 365 server::Activation::Activations::Active) 366 { 367 count++; 368 // Don't put the functional version on the queue since we can't 369 // remove the "running" PNOR version if it allows multiple PNORs 370 // But removing functional version if there is only one PNOR. 371 if (ACTIVE_PNOR_MAX_ALLOWED > 1 && 372 isVersionFunctional(iter.second->versionId)) 373 { 374 continue; 375 } 376 versionsPQ.push(std::make_pair( 377 iter.second->redundancyPriority.get()->priority(), 378 iter.second->versionId)); 379 } 380 } 381 382 // If the number of PNOR versions is over ACTIVE_PNOR_MAX_ALLOWED -1, 383 // remove the highest priority one(s). 384 while ((count >= ACTIVE_PNOR_MAX_ALLOWED) && (!versionsPQ.empty())) 385 { 386 erase(versionsPQ.top().second); 387 versionsPQ.pop(); 388 count--; 389 isSpaceFreed = true; 390 } 391 return isSpaceFreed; 392 } 393 394 std::string ItemUpdaterUbi::determineId(const std::string& symlinkPath) 395 { 396 if (!std::filesystem::exists(symlinkPath)) 397 { 398 return {}; 399 } 400 401 auto target = std::filesystem::canonical(symlinkPath).string(); 402 403 // check to make sure the target really exists 404 if (!std::filesystem::is_regular_file(target + "/" + PNOR_TOC_FILE)) 405 { 406 return {}; 407 } 408 // Get the image <id> from the symlink target 409 // for example /media/ro-2a1022fe 410 static const auto PNOR_RO_PREFIX_LEN = strlen(PNOR_RO_PREFIX); 411 return target.substr(PNOR_RO_PREFIX_LEN); 412 } 413 414 void GardResetUbi::reset() 415 { 416 // The GARD partition is currently misspelled "GUARD." This file path will 417 // need to be updated in the future. 418 auto path = std::filesystem::path(PNOR_PRSV_ACTIVE_PATH); 419 path /= "GUARD"; 420 421 utils::hiomapdSuspend(bus); 422 423 if (std::filesystem::is_regular_file(path)) 424 { 425 std::filesystem::remove(path); 426 } 427 428 utils::hiomapdResume(bus); 429 } 430 431 } // namespace updater 432 } // namespace software 433 } // namespace openpower 434