blob: d5940b63896a2600901976b50a05c1f8846adf08 [file] [log] [blame]
Lei YU322f3f42019-02-21 16:10:41 +08001#include "config.h"
2
3#include "item_updater_static.hpp"
4
Lei YUb53425d2019-02-22 11:38:40 +08005#include "activation_static.hpp"
Lei YUdec8cf92019-02-21 17:47:05 +08006#include "version.hpp"
7
Lei YUa7b4ade2019-02-25 17:49:29 +08008#include <array>
Lei YUdec8cf92019-02-21 17:47:05 +08009#include <cstring>
10#include <filesystem>
11#include <fstream>
12#include <phosphor-logging/elog-errors.hpp>
13#include <phosphor-logging/log.hpp>
Lei YUa2e67162019-02-22 17:35:24 +080014#include <sstream>
Lei YUdec8cf92019-02-21 17:47:05 +080015#include <string>
Lei YU5efca582019-02-25 17:05:29 +080016#include <tuple>
17#include <xyz/openbmc_project/Common/error.hpp>
Lei YUdec8cf92019-02-21 17:47:05 +080018
19using namespace sdbusplus::xyz::openbmc_project::Common::Error;
20using namespace phosphor::logging;
Lei YU5efca582019-02-25 17:05:29 +080021using sdbusplus::exception::SdBusError;
Lei YUdec8cf92019-02-21 17:47:05 +080022
23// When you see server:: you know we're referencing our base class
24namespace server = sdbusplus::xyz::openbmc_project::Software::server;
25namespace fs = std::filesystem;
26
27namespace utils
28{
29
30template <typename... Ts>
31std::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
Lei YU5efca582019-02-25 17:05:29 +080039// Returns return code and the stdout
Lei YUdec8cf92019-02-21 17:47:05 +080040template <typename... Ts>
Lei YU5efca582019-02-25 17:05:29 +080041std::pair<int, std::string> pflash(Ts const&... ts)
Lei YUdec8cf92019-02-21 17:47:05 +080042{
43 std::array<char, 512> buffer;
44 std::string cmd = concat_string("pflash", ts...);
45 std::stringstream result;
Lei YU5efca582019-02-25 17:05:29 +080046 int rc;
47 FILE* pipe = popen(cmd.c_str(), "r");
Lei YUdec8cf92019-02-21 17:47:05 +080048 if (!pipe)
49 {
50 throw std::runtime_error("popen() failed!");
51 }
Lei YU5efca582019-02-25 17:05:29 +080052 while (fgets(buffer.data(), buffer.size(), pipe) != nullptr)
Lei YUdec8cf92019-02-21 17:47:05 +080053 {
54 result << buffer.data();
55 }
Lei YU5efca582019-02-25 17:05:29 +080056 rc = pclose(pipe);
57 return {rc, result.str()};
Lei YUdec8cf92019-02-21 17:47:05 +080058}
59
60std::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
Lei YU5efca582019-02-25 17:05:29 +080081 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
Lei YUdec8cf92019-02-21 17:47:05 +080089 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
Lei YU6cecc9b2019-02-26 17:27:23 +0800117void pnorClear(const std::string& part, bool shouldEcc = true)
Lei YUa7b4ade2019-02-25 17:49:29 +0800118{
119 int rc;
120 std::tie(rc, std::ignore) =
Lei YU6cecc9b2019-02-26 17:27:23 +0800121 utils::pflash("-P", part, shouldEcc ? "-c" : "-e", "-f >/dev/null");
Lei YUa7b4ade2019-02-25 17:49:29 +0800122 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
Lei YU6cecc9b2019-02-26 17:27:23 +0800135// The pair contains the partition name and if it should use ECC clear
136using PartClear = std::pair<std::string, bool>;
137
138std::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
Lei YU07830762019-03-01 17:17:28 +0800150 auto pos = line.find('[');
151 if (pos == std::string::npos)
152 {
153 continue;
154 }
155 auto flags = line.substr(pos);
Lei YU6cecc9b2019-02-26 17:27:23 +0800156 if (flags.find('F') != std::string::npos)
157 {
158 // This is a partition to be cleared
Lei YU07830762019-03-01 17:17:28 +0800159 pos = line.find_first_of(' '); // After "ID=xx"
160 if (pos == std::string::npos)
161 {
162 continue;
163 }
164 line = line.substr(pos); // Skiping "ID=xx"
165
166 pos = line.find_first_not_of(' '); // After spaces
167 if (pos == std::string::npos)
168 {
169 continue;
170 }
171 line = line.substr(pos); // Skipping spaces
172
173 pos = line.find_first_of(' '); // The end of part name
174 if (pos == std::string::npos)
175 {
176 continue;
177 }
178 line = line.substr(0, pos); // The part name
179
Lei YU6cecc9b2019-02-26 17:27:23 +0800180 bool ecc = flags.find('C') != std::string::npos;
181 ret.emplace_back(line, ecc);
182 }
183 }
184 return ret;
185}
186
187// Get partitions that should be cleared
188std::vector<PartClear> getPartsToClear()
189{
190 const auto& [rc, pflashInfo] = pflash("-i | grep ^ID | grep 'F'");
191 return getPartsToClear(pflashInfo);
192}
193
Lei YUdec8cf92019-02-21 17:47:05 +0800194} // namespace utils
195
Lei YU322f3f42019-02-21 16:10:41 +0800196namespace openpower
197{
198namespace software
199{
200namespace updater
201{
Lei YU5efca582019-02-25 17:05:29 +0800202// TODO: Change paths once openbmc/openbmc#1663 is completed.
203constexpr auto MBOXD_INTERFACE = "org.openbmc.mboxd";
204constexpr auto MBOXD_PATH = "/org/openbmc/mboxd";
205
Lei YU322f3f42019-02-21 16:10:41 +0800206std::unique_ptr<Activation> ItemUpdaterStatic::createActivationObject(
207 const std::string& path, const std::string& versionId,
208 const std::string& extVersion,
209 sdbusplus::xyz::openbmc_project::Software::server::Activation::Activations
210 activationStatus,
211 AssociationList& assocs)
212{
Lei YUb53425d2019-02-22 11:38:40 +0800213 return std::make_unique<ActivationStatic>(
214 bus, path, *this, versionId, extVersion, activationStatus, assocs);
Lei YU322f3f42019-02-21 16:10:41 +0800215}
216
217std::unique_ptr<Version> ItemUpdaterStatic::createVersionObject(
218 const std::string& objPath, const std::string& versionId,
219 const std::string& versionString,
220 sdbusplus::xyz::openbmc_project::Software::server::Version::VersionPurpose
221 versionPurpose,
222 const std::string& filePath)
223{
Lei YUb53425d2019-02-22 11:38:40 +0800224 auto version = std::make_unique<Version>(
225 bus, objPath, *this, versionId, versionString, versionPurpose, filePath,
226 std::bind(&ItemUpdaterStatic::erase, this, std::placeholders::_1));
227 version->deleteObject = std::make_unique<Delete>(bus, objPath, *version);
228 return version;
Lei YU322f3f42019-02-21 16:10:41 +0800229}
230
231bool ItemUpdaterStatic::validateImage(const std::string& path)
232{
Lei YUb53425d2019-02-22 11:38:40 +0800233 // There is no need to validate static layout pnor
Lei YU322f3f42019-02-21 16:10:41 +0800234 return true;
235}
236
237void ItemUpdaterStatic::processPNORImage()
238{
Lei YUdec8cf92019-02-21 17:47:05 +0800239 auto fullVersion = utils::getPNORVersion();
240
241 const auto& [version, extendedVersion] = Version::getVersions(fullVersion);
242 auto id = Version::getId(version);
243
Lei YU91add6d2019-03-01 14:23:40 +0800244 if (id.empty())
245 {
246 // Possibly a corrupted PNOR
247 return;
248 }
249
Lei YUdec8cf92019-02-21 17:47:05 +0800250 auto activationState = server::Activation::Activations::Active;
251 if (version.empty())
252 {
253 log<level::ERR>("Failed to read version",
254 entry("VERSION=%s", fullVersion.c_str()));
255 activationState = server::Activation::Activations::Invalid;
256 }
257
258 if (extendedVersion.empty())
259 {
260 log<level::ERR>("Failed to read extendedVersion",
261 entry("VERSION=%s", fullVersion.c_str()));
262 activationState = server::Activation::Activations::Invalid;
263 }
264
265 auto purpose = server::Version::VersionPurpose::Host;
266 auto path = fs::path(SOFTWARE_OBJPATH) / id;
267 AssociationList associations = {};
268
269 if (activationState == server::Activation::Activations::Active)
270 {
271 // Create an association to the host inventory item
272 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
273 ACTIVATION_REV_ASSOCIATION,
274 HOST_INVENTORY_PATH));
275
276 // Create an active association since this image is active
277 createActiveAssociation(path);
278 }
279
Lei YUb53425d2019-02-22 11:38:40 +0800280 // Create Activation instance for this version.
281 activations.insert(std::make_pair(
282 id, std::make_unique<ActivationStatic>(bus, path, *this, id,
283 extendedVersion, activationState,
284 associations)));
285
286 // If Active, create RedundancyPriority instance for this version.
287 if (activationState == server::Activation::Activations::Active)
288 {
289 // For now only one PNOR is supported with static layout
290 activations.find(id)->second->redundancyPriority =
291 std::make_unique<RedundancyPriority>(
292 bus, path, *(activations.find(id)->second), 0);
293 }
294
Lei YUdec8cf92019-02-21 17:47:05 +0800295 // Create Version instance for this version.
296 auto versionPtr = std::make_unique<Version>(
297 bus, path, *this, id, version, purpose, "",
298 std::bind(&ItemUpdaterStatic::erase, this, std::placeholders::_1));
299 versionPtr->deleteObject = std::make_unique<Delete>(bus, path, *versionPtr);
300 versions.insert(std::make_pair(id, std::move(versionPtr)));
301
302 if (!id.empty())
303 {
304 updateFunctionalAssociation(id);
305 }
Lei YU322f3f42019-02-21 16:10:41 +0800306}
307
308void ItemUpdaterStatic::reset()
309{
Lei YU6cecc9b2019-02-26 17:27:23 +0800310 auto partitions = utils::getPartsToClear();
Lei YUa7b4ade2019-02-25 17:49:29 +0800311 std::vector<uint8_t> mboxdArgs;
312
313 // Suspend mboxd - no args required.
314 auto dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH,
315 MBOXD_INTERFACE, "cmd");
316 dbusCall.append(static_cast<uint8_t>(3), mboxdArgs);
317
318 try
319 {
320 bus.call_noreply(dbusCall);
321 }
322 catch (const SdBusError& e)
323 {
324 log<level::ERR>("Error in mboxd suspend call",
325 entry("ERROR=%s", e.what()));
326 elog<InternalFailure>();
327 }
328 for (auto p : partitions)
329 {
330 utils::pnorClear(p.first, p.second);
331 }
332
333 // Resume mboxd with arg 1, indicating that the flash was modified.
334 dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, MBOXD_INTERFACE,
335 "cmd");
336 mboxdArgs.push_back(1);
337 dbusCall.append(static_cast<uint8_t>(4), mboxdArgs);
338
339 try
340 {
341 bus.call_noreply(dbusCall);
342 }
343 catch (const SdBusError& e)
344 {
345 log<level::ERR>("Error in mboxd resume call",
346 entry("ERROR=%s", e.what()));
347 elog<InternalFailure>();
348 }
Lei YU322f3f42019-02-21 16:10:41 +0800349}
350
351bool ItemUpdaterStatic::isVersionFunctional(const std::string& versionId)
352{
Lei YUa2e67162019-02-22 17:35:24 +0800353 return versionId == functionalVersionId;
Lei YU322f3f42019-02-21 16:10:41 +0800354}
355
356void ItemUpdaterStatic::freePriority(uint8_t value,
357 const std::string& versionId)
358{
359}
360
361void ItemUpdaterStatic::deleteAll()
362{
Lei YUa2e67162019-02-22 17:35:24 +0800363 // Static layout has only one active and function pnor
364 // There is no implementation for this interface
Lei YU322f3f42019-02-21 16:10:41 +0800365}
366
Lei YU6da3dae2019-02-28 14:26:37 +0800367bool ItemUpdaterStatic::freeSpace()
Lei YU322f3f42019-02-21 16:10:41 +0800368{
Lei YUa2e67162019-02-22 17:35:24 +0800369 // For now assume static layout only has 1 active PNOR,
370 // so erase the active PNOR
371 for (const auto& iter : activations)
372 {
373 if (iter.second.get()->activation() ==
374 server::Activation::Activations::Active)
375 {
Lei YU6da3dae2019-02-28 14:26:37 +0800376 return erase(iter.second->versionId);
Lei YUa2e67162019-02-22 17:35:24 +0800377 }
378 }
Lei YU91add6d2019-03-01 14:23:40 +0800379 // No active PNOR means PNOR is empty or corrupted
380 return true;
Lei YUa2e67162019-02-22 17:35:24 +0800381}
382
383void ItemUpdaterStatic::updateFunctionalAssociation(
384 const std::string& versionId)
385{
386 functionalVersionId = versionId;
387 ItemUpdater::updateFunctionalAssociation(versionId);
Lei YU322f3f42019-02-21 16:10:41 +0800388}
389
Lei YU716de5b2019-03-01 16:03:53 +0800390void GardResetStatic::reset()
Lei YU322f3f42019-02-21 16:10:41 +0800391{
Lei YU5efca582019-02-25 17:05:29 +0800392 // Clear gard partition
393 std::vector<uint8_t> mboxdArgs;
Lei YU5efca582019-02-25 17:05:29 +0800394
395 auto dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH,
396 MBOXD_INTERFACE, "cmd");
397 // Suspend mboxd - no args required.
398 dbusCall.append(static_cast<uint8_t>(3), mboxdArgs);
399
400 try
401 {
402 bus.call_noreply(dbusCall);
403 }
404 catch (const SdBusError& e)
405 {
406 log<level::ERR>("Error in mboxd suspend call",
407 entry("ERROR=%s", e.what()));
408 elog<InternalFailure>();
409 }
410
411 // Clear guard partition
Lei YUa7b4ade2019-02-25 17:49:29 +0800412 utils::pnorClear("GUARD");
Lei YU5efca582019-02-25 17:05:29 +0800413
414 dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, MBOXD_INTERFACE,
415 "cmd");
416 // Resume mboxd with arg 1, indicating that the flash is modified.
417 mboxdArgs.push_back(1);
418 dbusCall.append(static_cast<uint8_t>(4), mboxdArgs);
419
420 try
421 {
422 bus.call_noreply(dbusCall);
423 }
424 catch (const SdBusError& e)
425 {
426 log<level::ERR>("Error in mboxd resume call",
427 entry("ERROR=%s", e.what()));
428 elog<InternalFailure>();
429 }
Lei YU322f3f42019-02-21 16:10:41 +0800430}
431
432} // namespace updater
433} // namespace software
434} // namespace openpower