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