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>
concat_string(const Ts &...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>
pflash(const Ts &...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
getPNORVersion()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] =
83 pflash("-P VERSION -r", versionFile.string(), "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
pnorClear(const std::string & part,bool shouldEcc=true)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
getPartsToClear(const std::string & info)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); // Skipping "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
getPartsToClear()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
createActivationObject(const std::string & path,const std::string & versionId,const std::string & extVersion,sdbusplus::xyz::openbmc_project::Software::server::Activation::Activations activationStatus,AssociationList & assocs)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
createVersionObject(const std::string & objPath,const std::string & versionId,const std::string & versionString,sdbusplus::xyz::openbmc_project::Software::server::Version::VersionPurpose versionPurpose,const std::string & filePath)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
validateImage(const std::string &)229 bool ItemUpdaterStatic::validateImage(const std::string&)
230 {
231 // There is no need to validate static layout pnor
232 return true;
233 }
234
processPNORImage()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(
271 std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
272 ACTIVATION_REV_ASSOCIATION, 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(
284 std::make_pair(id, std::make_unique<ActivationStatic>(
285 bus, path, *this, id, extendedVersion,
286 activationState, 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
reset()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
isVersionFunctional(const std::string & versionId)324 bool ItemUpdaterStatic::isVersionFunctional(const std::string& versionId)
325 {
326 return versionId == functionalVersionId;
327 }
328
freePriority(uint8_t,const std::string &)329 void ItemUpdaterStatic::freePriority(uint8_t, const std::string&) {}
330
deleteAll()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
freeSpace()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
updateFunctionalAssociation(const std::string & versionId)353 void ItemUpdaterStatic::updateFunctionalAssociation(
354 const std::string& versionId)
355 {
356 functionalVersionId = versionId;
357 ItemUpdater::updateFunctionalAssociation(versionId);
358 }
359
reset()360 void GardResetStatic::reset()
361 {
362 // Clear guard 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