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 // Create Activation instance for this version. 123 activations.insert( 124 std::make_pair(id, std::make_unique<ActivationUbi>( 125 bus, path, *this, id, extendedVersion, 126 activationState, associations))); 127 128 // If Active, create RedundancyPriority instance for this version. 129 if (activationState == server::Activation::Activations::Active) 130 { 131 uint8_t priority = std::numeric_limits<uint8_t>::max(); 132 if (!restoreFromFile(id, priority)) 133 { 134 log<level::ERR>("Unable to restore priority from file.", 135 entry("VERSIONID=%s", id.c_str())); 136 } 137 activations.find(id)->second->redundancyPriority = 138 std::make_unique<RedundancyPriorityUbi>( 139 bus, path, *(activations.find(id)->second), priority); 140 } 141 142 // Create Version instance for this version. 143 auto versionPtr = std::make_unique<Version>( 144 bus, path, *this, id, version, purpose, "", 145 std::bind(&ItemUpdaterUbi::erase, this, std::placeholders::_1)); 146 versionPtr->deleteObject = 147 std::make_unique<Delete>(bus, path, *versionPtr); 148 versions.insert(std::make_pair(id, std::move(versionPtr))); 149 } 150 else if (0 == iter.path().native().compare(0, PNOR_RW_PREFIX_LEN, 151 PNOR_RW_PREFIX)) 152 { 153 auto id = iter.path().native().substr(PNOR_RW_PREFIX_LEN); 154 auto roDir = PNOR_RO_PREFIX + id; 155 if (!fs::is_directory(roDir)) 156 { 157 log<level::ERR>("No corresponding read-only volume found.", 158 entry("DIRNAME=%s", roDir.c_str())); 159 ItemUpdaterUbi::erase(id); 160 } 161 } 162 } 163 164 // Look at the RO symlink to determine if there is a functional image 165 auto id = determineId(PNOR_RO_ACTIVE_PATH); 166 if (!id.empty()) 167 { 168 updateFunctionalAssociation(id); 169 } 170 return; 171 } 172 173 int ItemUpdaterUbi::validateSquashFSImage(const std::string& filePath) 174 { 175 auto file = fs::path(filePath) / squashFSImage; 176 if (fs::is_regular_file(file)) 177 { 178 return 0; 179 } 180 else 181 { 182 log<level::ERR>("Failed to find the SquashFS image."); 183 return -1; 184 } 185 } 186 187 void ItemUpdaterUbi::removeReadOnlyPartition(const std::string& versionId) 188 { 189 auto serviceFile = "obmc-flash-bios-ubiumount-ro@" + versionId + ".service"; 190 191 // Remove the read-only partitions. 192 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, 193 SYSTEMD_INTERFACE, "StartUnit"); 194 method.append(serviceFile, "replace"); 195 bus.call_noreply(method); 196 } 197 198 void ItemUpdaterUbi::removeReadWritePartition(const std::string& versionId) 199 { 200 auto serviceFile = "obmc-flash-bios-ubiumount-rw@" + versionId + ".service"; 201 202 // Remove the read-write partitions. 203 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, 204 SYSTEMD_INTERFACE, "StartUnit"); 205 method.append(serviceFile, "replace"); 206 bus.call_noreply(method); 207 } 208 209 void ItemUpdaterUbi::reset() 210 { 211 utils::hiomapdSuspend(bus); 212 213 constexpr static auto patchDir = "/usr/local/share/pnor"; 214 if (fs::is_directory(patchDir)) 215 { 216 for (const auto& iter : fs::directory_iterator(patchDir)) 217 { 218 fs::remove_all(iter); 219 } 220 } 221 222 // Clear the read-write partitions. 223 for (const auto& it : activations) 224 { 225 auto rwDir = PNOR_RW_PREFIX + it.first; 226 if (fs::is_directory(rwDir)) 227 { 228 for (const auto& iter : fs::directory_iterator(rwDir)) 229 { 230 fs::remove_all(iter); 231 } 232 } 233 } 234 235 // Clear the preserved partition. 236 if (fs::is_directory(PNOR_PRSV)) 237 { 238 for (const auto& iter : fs::directory_iterator(PNOR_PRSV)) 239 { 240 fs::remove_all(iter); 241 } 242 } 243 244 utils::hiomapdResume(bus); 245 } 246 247 bool ItemUpdaterUbi::isVersionFunctional(const std::string& versionId) 248 { 249 if (!fs::exists(PNOR_RO_ACTIVE_PATH)) 250 { 251 return false; 252 } 253 254 fs::path activeRO = fs::read_symlink(PNOR_RO_ACTIVE_PATH); 255 256 if (!fs::is_directory(activeRO)) 257 { 258 return false; 259 } 260 261 if (activeRO.string().find(versionId) == std::string::npos) 262 { 263 return false; 264 } 265 266 // active PNOR is the version we're checking 267 return true; 268 } 269 270 void ItemUpdaterUbi::freePriority(uint8_t value, const std::string& versionId) 271 { 272 // Versions with the lowest priority in front 273 std::priority_queue<std::pair<int, std::string>, 274 std::vector<std::pair<int, std::string>>, 275 std::greater<std::pair<int, std::string>>> 276 versionsPQ; 277 278 for (const auto& intf : activations) 279 { 280 if (intf.second->redundancyPriority) 281 { 282 versionsPQ.push(std::make_pair( 283 intf.second->redundancyPriority.get()->priority(), 284 intf.second->versionId)); 285 } 286 } 287 288 while (!versionsPQ.empty()) 289 { 290 if (versionsPQ.top().first == value && 291 versionsPQ.top().second != versionId) 292 { 293 // Increase priority by 1 and update its value 294 ++value; 295 storeToFile(versionsPQ.top().second, value); 296 auto it = activations.find(versionsPQ.top().second); 297 it->second->redundancyPriority.get()->sdbusplus::xyz:: 298 openbmc_project::Software::server::RedundancyPriority::priority( 299 value); 300 } 301 versionsPQ.pop(); 302 } 303 } 304 305 bool ItemUpdaterUbi::erase(std::string entryId) 306 { 307 if (!ItemUpdater::erase(entryId)) 308 { 309 return false; 310 } 311 312 // Remove priority persistence file 313 removeFile(entryId); 314 315 // Removing read-only and read-write partitions 316 removeReadWritePartition(entryId); 317 removeReadOnlyPartition(entryId); 318 319 return true; 320 } 321 322 void ItemUpdaterUbi::deleteAll() 323 { 324 auto chassisOn = isChassisOn(); 325 326 for (const auto& activationIt : activations) 327 { 328 if (isVersionFunctional(activationIt.first) && chassisOn) 329 { 330 continue; 331 } 332 else 333 { 334 ItemUpdaterUbi::erase(activationIt.first); 335 } 336 } 337 338 // Remove any remaining pnor-ro- or pnor-rw- volumes that do not match 339 // the current version. 340 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, 341 SYSTEMD_INTERFACE, "StartUnit"); 342 method.append("obmc-flash-bios-cleanup.service", "replace"); 343 bus.call_noreply(method); 344 } 345 346 // TODO: openbmc/openbmc#1402 Monitor flash usage 347 bool ItemUpdaterUbi::freeSpace() 348 { 349 bool isSpaceFreed = false; 350 // Versions with the highest priority in front 351 std::priority_queue<std::pair<int, std::string>, 352 std::vector<std::pair<int, std::string>>, 353 std::less<std::pair<int, std::string>>> 354 versionsPQ; 355 356 std::size_t count = 0; 357 for (const auto& iter : activations) 358 { 359 if (iter.second.get()->activation() == 360 server::Activation::Activations::Active) 361 { 362 count++; 363 // Don't put the functional version on the queue since we can't 364 // remove the "running" PNOR version if it allows multiple PNORs 365 // But removing functional version if there is only one PNOR. 366 if (ACTIVE_PNOR_MAX_ALLOWED > 1 && 367 isVersionFunctional(iter.second->versionId)) 368 { 369 continue; 370 } 371 versionsPQ.push(std::make_pair( 372 iter.second->redundancyPriority.get()->priority(), 373 iter.second->versionId)); 374 } 375 } 376 377 // If the number of PNOR versions is over ACTIVE_PNOR_MAX_ALLOWED -1, 378 // remove the highest priority one(s). 379 while ((count >= ACTIVE_PNOR_MAX_ALLOWED) && (!versionsPQ.empty())) 380 { 381 erase(versionsPQ.top().second); 382 versionsPQ.pop(); 383 count--; 384 isSpaceFreed = true; 385 } 386 return isSpaceFreed; 387 } 388 389 std::string ItemUpdaterUbi::determineId(const std::string& symlinkPath) 390 { 391 if (!fs::exists(symlinkPath)) 392 { 393 return {}; 394 } 395 396 auto target = fs::canonical(symlinkPath).string(); 397 398 // check to make sure the target really exists 399 if (!fs::is_regular_file(target + "/" + PNOR_TOC_FILE)) 400 { 401 return {}; 402 } 403 // Get the image <id> from the symlink target 404 // for example /media/ro-2a1022fe 405 static const auto PNOR_RO_PREFIX_LEN = strlen(PNOR_RO_PREFIX); 406 return target.substr(PNOR_RO_PREFIX_LEN); 407 } 408 409 void GardResetUbi::reset() 410 { 411 // The GARD partition is currently misspelled "GUARD." This file path will 412 // need to be updated in the future. 413 auto path = fs::path(PNOR_PRSV_ACTIVE_PATH); 414 path /= "GUARD"; 415 416 utils::hiomapdSuspend(bus); 417 418 if (fs::is_regular_file(path)) 419 { 420 fs::remove(path); 421 } 422 423 utils::hiomapdResume(bus); 424 } 425 426 } // namespace updater 427 } // namespace software 428 } // namespace openpower 429