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 auto activationState = server::Activation::Activations::Active; 222 if (version.empty()) 223 { 224 log<level::ERR>("Failed to read version", 225 entry("VERSION=%s", fullVersion.c_str())); 226 activationState = server::Activation::Activations::Invalid; 227 } 228 229 if (extendedVersion.empty()) 230 { 231 log<level::ERR>("Failed to read extendedVersion", 232 entry("VERSION=%s", fullVersion.c_str())); 233 activationState = server::Activation::Activations::Invalid; 234 } 235 236 auto purpose = server::Version::VersionPurpose::Host; 237 auto path = fs::path(SOFTWARE_OBJPATH) / id; 238 AssociationList associations = {}; 239 240 if (activationState == server::Activation::Activations::Active) 241 { 242 // Create an association to the host inventory item 243 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION, 244 ACTIVATION_REV_ASSOCIATION, 245 HOST_INVENTORY_PATH)); 246 247 // Create an active association since this image is active 248 createActiveAssociation(path); 249 } 250 251 // Create Activation instance for this version. 252 activations.insert(std::make_pair( 253 id, std::make_unique<ActivationStatic>(bus, path, *this, id, 254 extendedVersion, activationState, 255 associations))); 256 257 // If Active, create RedundancyPriority instance for this version. 258 if (activationState == server::Activation::Activations::Active) 259 { 260 // For now only one PNOR is supported with static layout 261 activations.find(id)->second->redundancyPriority = 262 std::make_unique<RedundancyPriority>( 263 bus, path, *(activations.find(id)->second), 0); 264 } 265 266 // Create Version instance for this version. 267 auto versionPtr = std::make_unique<Version>( 268 bus, path, *this, id, version, purpose, "", 269 std::bind(&ItemUpdaterStatic::erase, this, std::placeholders::_1)); 270 versionPtr->deleteObject = std::make_unique<Delete>(bus, path, *versionPtr); 271 versions.insert(std::make_pair(id, std::move(versionPtr))); 272 273 if (!id.empty()) 274 { 275 updateFunctionalAssociation(id); 276 } 277 } 278 279 void ItemUpdaterStatic::reset() 280 { 281 auto partitions = utils::getPartsToClear(); 282 std::vector<uint8_t> mboxdArgs; 283 284 // Suspend mboxd - no args required. 285 auto dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, 286 MBOXD_INTERFACE, "cmd"); 287 dbusCall.append(static_cast<uint8_t>(3), mboxdArgs); 288 289 try 290 { 291 bus.call_noreply(dbusCall); 292 } 293 catch (const SdBusError& e) 294 { 295 log<level::ERR>("Error in mboxd suspend call", 296 entry("ERROR=%s", e.what())); 297 elog<InternalFailure>(); 298 } 299 for (auto p : partitions) 300 { 301 utils::pnorClear(p.first, p.second); 302 } 303 304 // Resume mboxd with arg 1, indicating that the flash was modified. 305 dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, MBOXD_INTERFACE, 306 "cmd"); 307 mboxdArgs.push_back(1); 308 dbusCall.append(static_cast<uint8_t>(4), mboxdArgs); 309 310 try 311 { 312 bus.call_noreply(dbusCall); 313 } 314 catch (const SdBusError& e) 315 { 316 log<level::ERR>("Error in mboxd resume call", 317 entry("ERROR=%s", e.what())); 318 elog<InternalFailure>(); 319 } 320 } 321 322 bool ItemUpdaterStatic::isVersionFunctional(const std::string& versionId) 323 { 324 return versionId == functionalVersionId; 325 } 326 327 void ItemUpdaterStatic::freePriority(uint8_t value, 328 const std::string& versionId) 329 { 330 } 331 332 void ItemUpdaterStatic::deleteAll() 333 { 334 // Static layout has only one active and function pnor 335 // There is no implementation for this interface 336 } 337 338 bool ItemUpdaterStatic::freeSpace() 339 { 340 // For now assume static layout only has 1 active PNOR, 341 // so erase the active PNOR 342 for (const auto& iter : activations) 343 { 344 if (iter.second.get()->activation() == 345 server::Activation::Activations::Active) 346 { 347 return erase(iter.second->versionId); 348 } 349 } 350 return false; 351 } 352 353 void ItemUpdaterStatic::updateFunctionalAssociation( 354 const std::string& versionId) 355 { 356 functionalVersionId = versionId; 357 ItemUpdater::updateFunctionalAssociation(versionId); 358 } 359 360 void GardReset::reset() 361 { 362 // Clear gard partition 363 std::vector<uint8_t> mboxdArgs; 364 365 auto dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, 366 MBOXD_INTERFACE, "cmd"); 367 // Suspend mboxd - no args required. 368 dbusCall.append(static_cast<uint8_t>(3), mboxdArgs); 369 370 try 371 { 372 bus.call_noreply(dbusCall); 373 } 374 catch (const SdBusError& e) 375 { 376 log<level::ERR>("Error in mboxd suspend call", 377 entry("ERROR=%s", e.what())); 378 elog<InternalFailure>(); 379 } 380 381 // Clear guard partition 382 utils::pnorClear("GUARD"); 383 384 dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, MBOXD_INTERFACE, 385 "cmd"); 386 // Resume mboxd with arg 1, indicating that the flash is modified. 387 mboxdArgs.push_back(1); 388 dbusCall.append(static_cast<uint8_t>(4), mboxdArgs); 389 390 try 391 { 392 bus.call_noreply(dbusCall); 393 } 394 catch (const SdBusError& e) 395 { 396 log<level::ERR>("Error in mboxd resume call", 397 entry("ERROR=%s", e.what())); 398 elog<InternalFailure>(); 399 } 400 } 401 402 } // namespace updater 403 } // namespace software 404 } // namespace openpower 405