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