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 // TODO openbmc/openbmc#1896 Improve the performance of this function 273 for (const auto& intf : activations) 274 { 275 if (intf.second->redundancyPriority) 276 { 277 if (intf.second->redundancyPriority.get()->priority() == value && 278 intf.second->versionId != versionId) 279 { 280 intf.second->redundancyPriority.get()->priority(value + 1); 281 } 282 } 283 } 284 } 285 286 bool ItemUpdaterUbi::erase(std::string entryId) 287 { 288 if (!ItemUpdater::erase(entryId)) 289 { 290 return false; 291 } 292 293 // Remove priority persistence file 294 removeFile(entryId); 295 296 // Removing read-only and read-write partitions 297 removeReadWritePartition(entryId); 298 removeReadOnlyPartition(entryId); 299 300 return true; 301 } 302 303 void ItemUpdaterUbi::deleteAll() 304 { 305 auto chassisOn = isChassisOn(); 306 307 for (const auto& activationIt : activations) 308 { 309 if (isVersionFunctional(activationIt.first) && chassisOn) 310 { 311 continue; 312 } 313 else 314 { 315 ItemUpdaterUbi::erase(activationIt.first); 316 } 317 } 318 319 // Remove any remaining pnor-ro- or pnor-rw- volumes that do not match 320 // the current version. 321 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, 322 SYSTEMD_INTERFACE, "StartUnit"); 323 method.append("obmc-flash-bios-cleanup.service", "replace"); 324 bus.call_noreply(method); 325 } 326 327 // TODO: openbmc/openbmc#1402 Monitor flash usage 328 bool ItemUpdaterUbi::freeSpace() 329 { 330 bool isSpaceFreed = false; 331 // Versions with the highest priority in front 332 std::priority_queue<std::pair<int, std::string>, 333 std::vector<std::pair<int, std::string>>, 334 std::less<std::pair<int, std::string>>> 335 versionsPQ; 336 337 std::size_t count = 0; 338 for (const auto& iter : activations) 339 { 340 if (iter.second.get()->activation() == 341 server::Activation::Activations::Active) 342 { 343 count++; 344 // Don't put the functional version on the queue since we can't 345 // remove the "running" PNOR version if it allows multiple PNORs 346 // But removing functional version if there is only one PNOR. 347 if (ACTIVE_PNOR_MAX_ALLOWED > 1 && 348 isVersionFunctional(iter.second->versionId)) 349 { 350 continue; 351 } 352 versionsPQ.push(std::make_pair( 353 iter.second->redundancyPriority.get()->priority(), 354 iter.second->versionId)); 355 } 356 } 357 358 // If the number of PNOR versions is over ACTIVE_PNOR_MAX_ALLOWED -1, 359 // remove the highest priority one(s). 360 while ((count >= ACTIVE_PNOR_MAX_ALLOWED) && (!versionsPQ.empty())) 361 { 362 erase(versionsPQ.top().second); 363 versionsPQ.pop(); 364 count--; 365 isSpaceFreed = true; 366 } 367 return isSpaceFreed; 368 } 369 370 std::string ItemUpdaterUbi::determineId(const std::string& symlinkPath) 371 { 372 if (!fs::exists(symlinkPath)) 373 { 374 return {}; 375 } 376 377 auto target = fs::canonical(symlinkPath).string(); 378 379 // check to make sure the target really exists 380 if (!fs::is_regular_file(target + "/" + PNOR_TOC_FILE)) 381 { 382 return {}; 383 } 384 // Get the image <id> from the symlink target 385 // for example /media/ro-2a1022fe 386 static const auto PNOR_RO_PREFIX_LEN = strlen(PNOR_RO_PREFIX); 387 return target.substr(PNOR_RO_PREFIX_LEN); 388 } 389 390 void GardResetUbi::reset() 391 { 392 // The GARD partition is currently misspelled "GUARD." This file path will 393 // need to be updated in the future. 394 auto path = fs::path(PNOR_PRSV_ACTIVE_PATH); 395 path /= "GUARD"; 396 397 utils::hiomapdSuspend(bus); 398 399 if (fs::is_regular_file(path)) 400 { 401 fs::remove(path); 402 } 403 404 utils::hiomapdResume(bus); 405 } 406 407 } // namespace updater 408 } // namespace software 409 } // namespace openpower 410