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