blob: 61a8e01561a755fa19b8fdf11baa4fba8faebe1b [file] [log] [blame]
Lei YUf3ce4332019-02-21 14:09:49 +08001#include "config.h"
2
3#include "item_updater_ubi.hpp"
4
Lei YU9b21efc2019-02-21 15:52:53 +08005#include "activation_ubi.hpp"
Lei YUf3ce4332019-02-21 14:09:49 +08006#include "serialize.hpp"
7#include "version.hpp"
8#include "xyz/openbmc_project/Common/error.hpp"
9
10#include <experimental/filesystem>
11#include <fstream>
12#include <phosphor-logging/elog-errors.hpp>
13#include <phosphor-logging/log.hpp>
14#include <queue>
15#include <string>
16#include <xyz/openbmc_project/Software/Version/server.hpp>
17
18namespace openpower
19{
20namespace software
21{
22namespace updater
23{
24
25// When you see server:: you know we're referencing our base class
26namespace server = sdbusplus::xyz::openbmc_project::Software::server;
27namespace fs = std::experimental::filesystem;
28
29using namespace sdbusplus::xyz::openbmc_project::Common::Error;
30using namespace phosphor::logging;
31
32constexpr auto squashFSImage = "pnor.xz.squashfs";
33
34// TODO: Change paths once openbmc/openbmc#1663 is completed.
35constexpr auto MBOXD_INTERFACE = "org.openbmc.mboxd";
36constexpr auto MBOXD_PATH = "/org/openbmc/mboxd";
37
Lei YUa9ac9272019-02-22 16:38:35 +080038std::unique_ptr<Activation> ItemUpdaterUbi::createActivationObject(
39 const std::string& path, const std::string& versionId,
40 const std::string& extVersion,
41 sdbusplus::xyz::openbmc_project::Software::server::Activation::Activations
42 activationStatus,
43 AssociationList& assocs)
Lei YUf3ce4332019-02-21 14:09:49 +080044{
Lei YUa9ac9272019-02-22 16:38:35 +080045 return std::make_unique<ActivationUbi>(
46 bus, path, *this, versionId, extVersion, activationStatus, assocs);
47}
Lei YUf3ce4332019-02-21 14:09:49 +080048
Lei YUa9ac9272019-02-22 16:38:35 +080049std::unique_ptr<Version> ItemUpdaterUbi::createVersionObject(
50 const std::string& objPath, const std::string& versionId,
51 const std::string& versionString,
52 sdbusplus::xyz::openbmc_project::Software::server::Version::VersionPurpose
53 versionPurpose,
54 const std::string& filePath)
55{
56 auto version = std::make_unique<Version>(
57 bus, objPath, *this, versionId, versionString, versionPurpose, filePath,
58 std::bind(&ItemUpdaterUbi::erase, this, std::placeholders::_1));
59 version->deleteObject = std::make_unique<Delete>(bus, objPath, *version);
60 return version;
61}
Lei YUf3ce4332019-02-21 14:09:49 +080062
Lei YUa9ac9272019-02-22 16:38:35 +080063bool ItemUpdaterUbi::validateImage(const std::string& path)
64{
65 return validateSquashFSImage(path) == 0;
Lei YUf3ce4332019-02-21 14:09:49 +080066}
67
68void ItemUpdaterUbi::processPNORImage()
69{
70 // Read pnor.toc from folders under /media/
71 // to get Active Software Versions.
72 for (const auto& iter : fs::directory_iterator(MEDIA_DIR))
73 {
74 auto activationState = server::Activation::Activations::Active;
75
76 static const auto PNOR_RO_PREFIX_LEN = strlen(PNOR_RO_PREFIX);
77 static const auto PNOR_RW_PREFIX_LEN = strlen(PNOR_RW_PREFIX);
78
79 // Check if the PNOR_RO_PREFIX is the prefix of the iter.path
80 if (0 ==
81 iter.path().native().compare(0, PNOR_RO_PREFIX_LEN, PNOR_RO_PREFIX))
82 {
83 // The versionId is extracted from the path
84 // for example /media/pnor-ro-2a1022fe.
85 auto id = iter.path().native().substr(PNOR_RO_PREFIX_LEN);
86 auto pnorTOC = iter.path() / PNOR_TOC_FILE;
87 if (!fs::is_regular_file(pnorTOC))
88 {
89 log<level::ERR>("Failed to read pnorTOC.",
90 entry("FILENAME=%s", pnorTOC.c_str()));
91 ItemUpdaterUbi::erase(id);
92 continue;
93 }
94 auto keyValues = Version::getValue(
95 pnorTOC, {{"version", ""}, {"extended_version", ""}});
96 auto& version = keyValues.at("version");
97 if (version.empty())
98 {
99 log<level::ERR>("Failed to read version from pnorTOC",
100 entry("FILENAME=%s", pnorTOC.c_str()));
101 activationState = server::Activation::Activations::Invalid;
102 }
103
104 auto& extendedVersion = keyValues.at("extended_version");
105 if (extendedVersion.empty())
106 {
107 log<level::ERR>("Failed to read extendedVersion from pnorTOC",
108 entry("FILENAME=%s", pnorTOC.c_str()));
109 activationState = server::Activation::Activations::Invalid;
110 }
111
112 auto purpose = server::Version::VersionPurpose::Host;
113 auto path = fs::path(SOFTWARE_OBJPATH) / id;
114 AssociationList associations = {};
115
116 if (activationState == server::Activation::Activations::Active)
117 {
118 // Create an association to the host inventory item
119 associations.emplace_back(std::make_tuple(
120 ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION,
121 HOST_INVENTORY_PATH));
122
123 // Create an active association since this image is active
124 createActiveAssociation(path);
125 }
126
127 // Create Activation instance for this version.
128 activations.insert(
Lei YU9b21efc2019-02-21 15:52:53 +0800129 std::make_pair(id, std::make_unique<ActivationUbi>(
Lei YUf3ce4332019-02-21 14:09:49 +0800130 bus, path, *this, id, extendedVersion,
131 activationState, associations)));
132
133 // If Active, create RedundancyPriority instance for this version.
134 if (activationState == server::Activation::Activations::Active)
135 {
136 uint8_t priority = std::numeric_limits<uint8_t>::max();
137 if (!restoreFromFile(id, priority))
138 {
139 log<level::ERR>("Unable to restore priority from file.",
140 entry("VERSIONID=%s", id.c_str()));
141 }
142 activations.find(id)->second->redundancyPriority =
Lei YU9b21efc2019-02-21 15:52:53 +0800143 std::make_unique<RedundancyPriorityUbi>(
Lei YUf3ce4332019-02-21 14:09:49 +0800144 bus, path, *(activations.find(id)->second), priority);
145 }
146
147 // Create Version instance for this version.
148 auto versionPtr = std::make_unique<Version>(
149 bus, path, *this, id, version, purpose, "",
150 std::bind(&ItemUpdaterUbi::erase, this, std::placeholders::_1));
151 versionPtr->deleteObject =
152 std::make_unique<Delete>(bus, path, *versionPtr);
153 versions.insert(std::make_pair(id, std::move(versionPtr)));
154 }
155 else if (0 == iter.path().native().compare(0, PNOR_RW_PREFIX_LEN,
156 PNOR_RW_PREFIX))
157 {
158 auto id = iter.path().native().substr(PNOR_RW_PREFIX_LEN);
159 auto roDir = PNOR_RO_PREFIX + id;
160 if (!fs::is_directory(roDir))
161 {
162 log<level::ERR>("No corresponding read-only volume found.",
163 entry("DIRNAME=%s", roDir.c_str()));
164 ItemUpdaterUbi::erase(id);
165 }
166 }
167 }
168
169 // Look at the RO symlink to determine if there is a functional image
170 auto id = determineId(PNOR_RO_ACTIVE_PATH);
171 if (!id.empty())
172 {
173 updateFunctionalAssociation(id);
174 }
175 return;
176}
177
178int ItemUpdaterUbi::validateSquashFSImage(const std::string& filePath)
179{
180 auto file = fs::path(filePath) / squashFSImage;
181 if (fs::is_regular_file(file))
182 {
183 return 0;
184 }
185 else
186 {
187 log<level::ERR>("Failed to find the SquashFS image.");
188 return -1;
189 }
190}
191
192void ItemUpdaterUbi::removeReadOnlyPartition(std::string versionId)
193{
194 auto serviceFile = "obmc-flash-bios-ubiumount-ro@" + versionId + ".service";
195
196 // Remove the read-only partitions.
197 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
198 SYSTEMD_INTERFACE, "StartUnit");
199 method.append(serviceFile, "replace");
200 bus.call_noreply(method);
201}
202
203void ItemUpdaterUbi::removeReadWritePartition(std::string versionId)
204{
205 auto serviceFile = "obmc-flash-bios-ubiumount-rw@" + versionId + ".service";
206
207 // Remove the read-write partitions.
208 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
209 SYSTEMD_INTERFACE, "StartUnit");
210 method.append(serviceFile, "replace");
211 bus.call_noreply(method);
212}
213
214void ItemUpdaterUbi::reset()
215{
216 std::vector<uint8_t> mboxdArgs;
217
218 // Suspend mboxd - no args required.
219 auto dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH,
220 MBOXD_INTERFACE, "cmd");
221
222 dbusCall.append(static_cast<uint8_t>(3), mboxdArgs);
223
224 auto responseMsg = bus.call(dbusCall);
225 if (responseMsg.is_method_error())
226 {
227 log<level::ERR>("Error in mboxd suspend call");
228 elog<InternalFailure>();
229 }
230
231 constexpr static auto patchDir = "/usr/local/share/pnor";
232 if (fs::is_directory(patchDir))
233 {
234 for (const auto& iter : fs::directory_iterator(patchDir))
235 {
236 fs::remove_all(iter);
237 }
238 }
239
240 // Clear the read-write partitions.
241 for (const auto& it : activations)
242 {
243 auto rwDir = PNOR_RW_PREFIX + it.first;
244 if (fs::is_directory(rwDir))
245 {
246 for (const auto& iter : fs::directory_iterator(rwDir))
247 {
248 fs::remove_all(iter);
249 }
250 }
251 }
252
253 // Clear the preserved partition.
254 if (fs::is_directory(PNOR_PRSV))
255 {
256 for (const auto& iter : fs::directory_iterator(PNOR_PRSV))
257 {
258 fs::remove_all(iter);
259 }
260 }
261
262 // Resume mboxd with arg 1, indicating that the flash was modified.
263 dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, MBOXD_INTERFACE,
264 "cmd");
265
266 mboxdArgs.push_back(1);
267 dbusCall.append(static_cast<uint8_t>(4), mboxdArgs);
268
269 responseMsg = bus.call(dbusCall);
270 if (responseMsg.is_method_error())
271 {
272 log<level::ERR>("Error in mboxd resume call");
273 elog<InternalFailure>();
274 }
275
276 return;
277}
278
279bool ItemUpdaterUbi::isVersionFunctional(const std::string& versionId)
280{
281 if (!fs::exists(PNOR_RO_ACTIVE_PATH))
282 {
283 return false;
284 }
285
286 fs::path activeRO = fs::read_symlink(PNOR_RO_ACTIVE_PATH);
287
288 if (!fs::is_directory(activeRO))
289 {
290 return false;
291 }
292
293 if (activeRO.string().find(versionId) == std::string::npos)
294 {
295 return false;
296 }
297
298 // active PNOR is the version we're checking
299 return true;
300}
301
302void ItemUpdaterUbi::freePriority(uint8_t value, const std::string& versionId)
303{
304 // TODO openbmc/openbmc#1896 Improve the performance of this function
305 for (const auto& intf : activations)
306 {
307 if (intf.second->redundancyPriority)
308 {
309 if (intf.second->redundancyPriority.get()->priority() == value &&
310 intf.second->versionId != versionId)
311 {
312 intf.second->redundancyPriority.get()->priority(value + 1);
313 }
314 }
315 }
316}
317
Lei YUf3ce4332019-02-21 14:09:49 +0800318bool ItemUpdaterUbi::erase(std::string entryId)
319{
320 if (!ItemUpdater::erase(entryId))
321 {
322 return false;
323 }
324
325 // Remove priority persistence file
326 removeFile(entryId);
327
328 // Removing read-only and read-write partitions
329 removeReadWritePartition(entryId);
330 removeReadOnlyPartition(entryId);
331
332 return true;
333}
334
335void ItemUpdaterUbi::deleteAll()
336{
337 auto chassisOn = isChassisOn();
338
339 for (const auto& activationIt : activations)
340 {
341 if (isVersionFunctional(activationIt.first) && chassisOn)
342 {
343 continue;
344 }
345 else
346 {
347 ItemUpdaterUbi::erase(activationIt.first);
348 }
349 }
350
351 // Remove any remaining pnor-ro- or pnor-rw- volumes that do not match
352 // the current version.
353 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
354 SYSTEMD_INTERFACE, "StartUnit");
355 method.append("obmc-flash-bios-cleanup.service", "replace");
356 bus.call_noreply(method);
357}
358
359// TODO: openbmc/openbmc#1402 Monitor flash usage
Lei YU6da3dae2019-02-28 14:26:37 +0800360bool ItemUpdaterUbi::freeSpace()
Lei YUf3ce4332019-02-21 14:09:49 +0800361{
Lei YU6da3dae2019-02-28 14:26:37 +0800362 bool isSpaceFreed = false;
Lei YUf3ce4332019-02-21 14:09:49 +0800363 // Versions with the highest priority in front
364 std::priority_queue<std::pair<int, std::string>,
365 std::vector<std::pair<int, std::string>>,
366 std::less<std::pair<int, std::string>>>
367 versionsPQ;
368
369 std::size_t count = 0;
370 for (const auto& iter : activations)
371 {
372 if (iter.second.get()->activation() ==
373 server::Activation::Activations::Active)
374 {
375 count++;
376 // Don't put the functional version on the queue since we can't
377 // remove the "running" PNOR version if it allows multiple PNORs
378 // But removing functional version if there is only one PNOR.
379 if (ACTIVE_PNOR_MAX_ALLOWED > 1 &&
380 isVersionFunctional(iter.second->versionId))
381 {
382 continue;
383 }
384 versionsPQ.push(std::make_pair(
385 iter.second->redundancyPriority.get()->priority(),
386 iter.second->versionId));
387 }
388 }
389
390 // If the number of PNOR versions is over ACTIVE_PNOR_MAX_ALLOWED -1,
391 // remove the highest priority one(s).
392 while ((count >= ACTIVE_PNOR_MAX_ALLOWED) && (!versionsPQ.empty()))
393 {
394 erase(versionsPQ.top().second);
395 versionsPQ.pop();
396 count--;
Lei YU6da3dae2019-02-28 14:26:37 +0800397 isSpaceFreed = true;
Lei YUf3ce4332019-02-21 14:09:49 +0800398 }
Lei YU6da3dae2019-02-28 14:26:37 +0800399 return isSpaceFreed;
Lei YUf3ce4332019-02-21 14:09:49 +0800400}
401
Lei YUbee51402019-02-26 11:36:34 +0800402std::string ItemUpdaterUbi::determineId(const std::string& symlinkPath)
Lei YUf3ce4332019-02-21 14:09:49 +0800403{
404 if (!fs::exists(symlinkPath))
405 {
406 return {};
407 }
408
409 auto target = fs::canonical(symlinkPath).string();
410
411 // check to make sure the target really exists
412 if (!fs::is_regular_file(target + "/" + PNOR_TOC_FILE))
413 {
414 return {};
415 }
416 // Get the image <id> from the symlink target
417 // for example /media/ro-2a1022fe
418 static const auto PNOR_RO_PREFIX_LEN = strlen(PNOR_RO_PREFIX);
419 return target.substr(PNOR_RO_PREFIX_LEN);
420}
421
422void GardReset::reset()
423{
424 // The GARD partition is currently misspelled "GUARD." This file path will
425 // need to be updated in the future.
426 auto path = fs::path(PNOR_PRSV_ACTIVE_PATH);
427 path /= "GUARD";
428 std::vector<uint8_t> mboxdArgs;
429
430 auto dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH,
431 MBOXD_INTERFACE, "cmd");
432
433 // Suspend mboxd - no args required.
434 dbusCall.append(static_cast<uint8_t>(3), mboxdArgs);
435
436 auto responseMsg = bus.call(dbusCall);
437 if (responseMsg.is_method_error())
438 {
439 log<level::ERR>("Error in mboxd suspend call");
440 elog<InternalFailure>();
441 }
442
443 if (fs::is_regular_file(path))
444 {
445 fs::remove(path);
446 }
447
448 dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, MBOXD_INTERFACE,
449 "cmd");
450
451 // Resume mboxd with arg 1, indicating that the flash is modified.
452 mboxdArgs.push_back(1);
453 dbusCall.append(static_cast<uint8_t>(4), mboxdArgs);
454
455 responseMsg = bus.call(dbusCall);
456 if (responseMsg.is_method_error())
457 {
458 log<level::ERR>("Error in mboxd resume call");
459 elog<InternalFailure>();
460 }
461}
462
463} // namespace updater
464} // namespace software
465} // namespace openpower