1 #include "config.h" 2 3 #include "item_updater_static.hpp" 4 5 #include "activation_static.hpp" 6 #include "utils.hpp" 7 #include "version.hpp" 8 9 #include <phosphor-logging/elog-errors.hpp> 10 #include <phosphor-logging/log.hpp> 11 #include <xyz/openbmc_project/Common/error.hpp> 12 13 #include <array> 14 #include <cstring> 15 #include <filesystem> 16 #include <fstream> 17 #include <sstream> 18 #include <string> 19 #include <tuple> 20 21 using namespace sdbusplus::xyz::openbmc_project::Common::Error; 22 using namespace phosphor::logging; 23 24 // When you see server:: you know we're referencing our base class 25 namespace server = sdbusplus::xyz::openbmc_project::Software::server; 26 namespace fs = std::filesystem; 27 28 namespace utils 29 { 30 31 template <typename... Ts> 32 std::string concat_string(const Ts&... ts) 33 { 34 std::stringstream s; 35 ((s << ts << " "), ...) << std::endl; 36 return s.str(); 37 } 38 39 // Helper function to run pflash command 40 // Returns return code and the stdout 41 template <typename... Ts> 42 std::pair<int, std::string> pflash(const Ts&... ts) 43 { 44 std::array<char, 512> buffer; 45 std::string cmd = concat_string("pflash", ts...); 46 std::stringstream result; 47 int rc; 48 FILE* pipe = popen(cmd.c_str(), "r"); 49 if (!pipe) 50 { 51 throw std::runtime_error("popen() failed!"); 52 } 53 while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) 54 { 55 result << buffer.data(); 56 } 57 rc = pclose(pipe); 58 return {rc, result.str()}; 59 } 60 61 std::string getPNORVersion() 62 { 63 // A signed version partition will have an extra 4K header starting with 64 // the magic number 17082011 in big endian: 65 // https://github.com/open-power/skiboot/blob/master/libstb/container.h#L47 66 67 constexpr uint8_t MAGIC[] = {0x17, 0x08, 0x20, 0x11}; 68 constexpr auto MAGIC_SIZE = sizeof(MAGIC); 69 static_assert(MAGIC_SIZE == 4); 70 71 auto tmp = fs::temp_directory_path(); 72 std::string tmpDir(tmp / "versionXXXXXX"); 73 if (!mkdtemp(tmpDir.data())) 74 { 75 log<level::ERR>("Failed to create temp dir"); 76 return {}; 77 } 78 79 fs::path versionFile = tmpDir; 80 versionFile /= "version"; 81 82 auto [rc, r] = pflash("-P VERSION -r", versionFile.string(), 83 "2>&1 > /dev/null"); 84 if (rc != 0) 85 { 86 log<level::ERR>("Failed to read VERSION", entry("RETURNCODE=%d", rc)); 87 return {}; 88 } 89 90 std::ifstream f(versionFile.c_str(), std::ios::in | std::ios::binary); 91 uint8_t magic[MAGIC_SIZE]; 92 std::string version; 93 94 f.read(reinterpret_cast<char*>(magic), MAGIC_SIZE); 95 f.seekg(0, std::ios::beg); 96 if (std::memcmp(magic, MAGIC, MAGIC_SIZE) == 0) 97 { 98 // Skip the first 4K header 99 f.ignore(4096); 100 } 101 102 getline(f, version, '\0'); 103 f.close(); 104 105 // Clear the temp dir 106 std::error_code ec; 107 fs::remove_all(tmpDir, ec); 108 if (ec) 109 { 110 log<level::ERR>("Failed to remove temp dir", 111 entry("DIR=%s", tmpDir.c_str()), 112 entry("ERR=%s", ec.message().c_str())); 113 } 114 115 return version; 116 } 117 118 void pnorClear(const std::string& part, bool shouldEcc = true) 119 { 120 int rc; 121 std::tie(rc, std::ignore) = 122 utils::pflash("-P", part, shouldEcc ? "-c" : "-e", "-f >/dev/null"); 123 if (rc != 0) 124 { 125 log<level::ERR>("Failed to clear partition", 126 entry("PART=%s", part.c_str()), 127 entry("RETURNCODE=%d", rc)); 128 } 129 else 130 { 131 log<level::INFO>("Clear partition successfully", 132 entry("PART=%s", part.c_str())); 133 } 134 } 135 136 // The pair contains the partition name and if it should use ECC clear 137 using PartClear = std::pair<std::string, bool>; 138 139 std::vector<PartClear> getPartsToClear(const std::string& info) 140 { 141 std::vector<PartClear> ret; 142 std::istringstream iss(info); 143 std::string line; 144 145 while (std::getline(iss, line)) 146 { 147 // Each line looks like 148 // ID=06 MVPD 0x0012d000..0x001bd000 (actual=0x00090000) [E--P--F-C-] 149 // Flag 'F' means REPROVISION 150 // Flag 'E' means ECC required 151 auto pos = line.find('['); 152 if (pos == std::string::npos) 153 { 154 continue; 155 } 156 auto flags = line.substr(pos); 157 if (flags.find('F') != std::string::npos) 158 { 159 // This is a partition to be cleared 160 pos = line.find_first_of(' '); // After "ID=xx" 161 if (pos == std::string::npos) 162 { 163 continue; 164 } 165 line = line.substr(pos); // Skiping "ID=xx" 166 167 pos = line.find_first_not_of(' '); // After spaces 168 if (pos == std::string::npos) 169 { 170 continue; 171 } 172 line = line.substr(pos); // Skipping spaces 173 174 pos = line.find_first_of(' '); // The end of part name 175 if (pos == std::string::npos) 176 { 177 continue; 178 } 179 line = line.substr(0, pos); // The part name 180 181 bool ecc = flags.find('E') != std::string::npos; 182 ret.emplace_back(line, ecc); 183 } 184 } 185 return ret; 186 } 187 188 // Get partitions that should be cleared 189 std::vector<PartClear> getPartsToClear() 190 { 191 const auto& [rc, pflashInfo] = pflash("-i | grep ^ID | grep 'F'"); 192 return getPartsToClear(pflashInfo); 193 } 194 195 } // namespace utils 196 197 namespace openpower 198 { 199 namespace software 200 { 201 namespace updater 202 { 203 204 std::unique_ptr<Activation> ItemUpdaterStatic::createActivationObject( 205 const std::string& path, const std::string& versionId, 206 const std::string& extVersion, 207 sdbusplus::xyz::openbmc_project::Software::server::Activation::Activations 208 activationStatus, 209 AssociationList& assocs) 210 { 211 return std::make_unique<ActivationStatic>( 212 bus, path, *this, versionId, extVersion, activationStatus, assocs); 213 } 214 215 std::unique_ptr<Version> ItemUpdaterStatic::createVersionObject( 216 const std::string& objPath, const std::string& versionId, 217 const std::string& versionString, 218 sdbusplus::xyz::openbmc_project::Software::server::Version::VersionPurpose 219 versionPurpose, 220 const std::string& filePath) 221 { 222 auto version = std::make_unique<Version>( 223 bus, objPath, *this, versionId, versionString, versionPurpose, filePath, 224 std::bind(&ItemUpdaterStatic::erase, this, std::placeholders::_1)); 225 version->deleteObject = std::make_unique<Delete>(bus, objPath, *version); 226 return version; 227 } 228 229 bool ItemUpdaterStatic::validateImage(const std::string&) 230 { 231 // There is no need to validate static layout pnor 232 return true; 233 } 234 235 void ItemUpdaterStatic::processPNORImage() 236 { 237 auto fullVersion = utils::getPNORVersion(); 238 239 const auto& [version, extendedVersion] = Version::getVersions(fullVersion); 240 auto id = Version::getId(version); 241 242 if (id.empty()) 243 { 244 // Possibly a corrupted PNOR 245 return; 246 } 247 248 auto activationState = server::Activation::Activations::Active; 249 if (version.empty()) 250 { 251 log<level::ERR>("Failed to read version", 252 entry("VERSION=%s", fullVersion.c_str())); 253 activationState = server::Activation::Activations::Invalid; 254 } 255 256 if (extendedVersion.empty()) 257 { 258 log<level::ERR>("Failed to read extendedVersion", 259 entry("VERSION=%s", fullVersion.c_str())); 260 activationState = server::Activation::Activations::Invalid; 261 } 262 263 auto purpose = server::Version::VersionPurpose::Host; 264 auto path = fs::path(SOFTWARE_OBJPATH) / id; 265 AssociationList associations = {}; 266 267 if (activationState == server::Activation::Activations::Active) 268 { 269 // Create an association to the host inventory item 270 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION, 271 ACTIVATION_REV_ASSOCIATION, 272 HOST_INVENTORY_PATH)); 273 274 // Create an active association since this image is active 275 createActiveAssociation(path); 276 } 277 278 // All updateable firmware components must expose the updateable 279 // association. 280 createUpdateableAssociation(path); 281 282 // Create Activation instance for this version. 283 activations.insert(std::make_pair( 284 id, std::make_unique<ActivationStatic>(bus, path, *this, id, 285 extendedVersion, activationState, 286 associations))); 287 288 // If Active, create RedundancyPriority instance for this version. 289 if (activationState == server::Activation::Activations::Active) 290 { 291 // For now only one PNOR is supported with static layout 292 activations.find(id)->second->redundancyPriority = 293 std::make_unique<RedundancyPriority>( 294 bus, path, *(activations.find(id)->second), 0); 295 } 296 297 // Create Version instance for this version. 298 auto versionPtr = std::make_unique<Version>( 299 bus, path, *this, id, version, purpose, "", 300 std::bind(&ItemUpdaterStatic::erase, this, std::placeholders::_1)); 301 versionPtr->deleteObject = std::make_unique<Delete>(bus, path, *versionPtr); 302 versions.insert(std::make_pair(id, std::move(versionPtr))); 303 304 if (!id.empty()) 305 { 306 updateFunctionalAssociation(id); 307 } 308 } 309 310 void ItemUpdaterStatic::reset() 311 { 312 auto partitions = utils::getPartsToClear(); 313 314 utils::hiomapdSuspend(bus); 315 316 for (auto p : partitions) 317 { 318 utils::pnorClear(p.first, p.second); 319 } 320 321 utils::hiomapdResume(bus); 322 } 323 324 bool ItemUpdaterStatic::isVersionFunctional(const std::string& versionId) 325 { 326 return versionId == functionalVersionId; 327 } 328 329 void ItemUpdaterStatic::freePriority(uint8_t, const std::string&) {} 330 331 void ItemUpdaterStatic::deleteAll() 332 { 333 // Static layout has only one active and function pnor 334 // There is no implementation for this interface 335 } 336 337 bool ItemUpdaterStatic::freeSpace() 338 { 339 // For now assume static layout only has 1 active PNOR, 340 // so erase the active PNOR 341 for (const auto& iter : activations) 342 { 343 if (iter.second.get()->activation() == 344 server::Activation::Activations::Active) 345 { 346 return erase(iter.second->versionId); 347 } 348 } 349 // No active PNOR means PNOR is empty or corrupted 350 return true; 351 } 352 353 void ItemUpdaterStatic::updateFunctionalAssociation( 354 const std::string& versionId) 355 { 356 functionalVersionId = versionId; 357 ItemUpdater::updateFunctionalAssociation(versionId); 358 } 359 360 void GardResetStatic::reset() 361 { 362 // Clear gard partition 363 utils::hiomapdSuspend(bus); 364 365 utils::pnorClear("GUARD"); 366 367 utils::hiomapdResume(bus); 368 } 369 370 } // namespace updater 371 } // namespace software 372 } // namespace openpower 373