blob: 875847f6e3ab3952985431a4f405e9da778720ab [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
360void ItemUpdaterUbi::freeSpace()
361{
362 // Versions with the highest priority in front
363 std::priority_queue<std::pair<int, std::string>,
364 std::vector<std::pair<int, std::string>>,
365 std::less<std::pair<int, std::string>>>
366 versionsPQ;
367
368 std::size_t count = 0;
369 for (const auto& iter : activations)
370 {
371 if (iter.second.get()->activation() ==
372 server::Activation::Activations::Active)
373 {
374 count++;
375 // Don't put the functional version on the queue since we can't
376 // remove the "running" PNOR version if it allows multiple PNORs
377 // But removing functional version if there is only one PNOR.
378 if (ACTIVE_PNOR_MAX_ALLOWED > 1 &&
379 isVersionFunctional(iter.second->versionId))
380 {
381 continue;
382 }
383 versionsPQ.push(std::make_pair(
384 iter.second->redundancyPriority.get()->priority(),
385 iter.second->versionId));
386 }
387 }
388
389 // If the number of PNOR versions is over ACTIVE_PNOR_MAX_ALLOWED -1,
390 // remove the highest priority one(s).
391 while ((count >= ACTIVE_PNOR_MAX_ALLOWED) && (!versionsPQ.empty()))
392 {
393 erase(versionsPQ.top().second);
394 versionsPQ.pop();
395 count--;
396 }
397}
398
Lei YUbee51402019-02-26 11:36:34 +0800399std::string ItemUpdaterUbi::determineId(const std::string& symlinkPath)
Lei YUf3ce4332019-02-21 14:09:49 +0800400{
401 if (!fs::exists(symlinkPath))
402 {
403 return {};
404 }
405
406 auto target = fs::canonical(symlinkPath).string();
407
408 // check to make sure the target really exists
409 if (!fs::is_regular_file(target + "/" + PNOR_TOC_FILE))
410 {
411 return {};
412 }
413 // Get the image <id> from the symlink target
414 // for example /media/ro-2a1022fe
415 static const auto PNOR_RO_PREFIX_LEN = strlen(PNOR_RO_PREFIX);
416 return target.substr(PNOR_RO_PREFIX_LEN);
417}
418
419void GardReset::reset()
420{
421 // The GARD partition is currently misspelled "GUARD." This file path will
422 // need to be updated in the future.
423 auto path = fs::path(PNOR_PRSV_ACTIVE_PATH);
424 path /= "GUARD";
425 std::vector<uint8_t> mboxdArgs;
426
427 auto dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH,
428 MBOXD_INTERFACE, "cmd");
429
430 // Suspend mboxd - no args required.
431 dbusCall.append(static_cast<uint8_t>(3), mboxdArgs);
432
433 auto responseMsg = bus.call(dbusCall);
434 if (responseMsg.is_method_error())
435 {
436 log<level::ERR>("Error in mboxd suspend call");
437 elog<InternalFailure>();
438 }
439
440 if (fs::is_regular_file(path))
441 {
442 fs::remove(path);
443 }
444
445 dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, MBOXD_INTERFACE,
446 "cmd");
447
448 // Resume mboxd with arg 1, indicating that the flash is modified.
449 mboxdArgs.push_back(1);
450 dbusCall.append(static_cast<uint8_t>(4), mboxdArgs);
451
452 responseMsg = bus.call(dbusCall);
453 if (responseMsg.is_method_error())
454 {
455 log<level::ERR>("Error in mboxd resume call");
456 elog<InternalFailure>();
457 }
458}
459
460} // namespace updater
461} // namespace software
462} // namespace openpower