blob: f576b3c284b722122b5173e243aed1ee70022786 [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
38void ItemUpdaterUbi::createActivation(sdbusplus::message::message& m)
39{
40 using SVersion = server::Version;
41 using VersionPurpose = SVersion::VersionPurpose;
42 namespace msg = sdbusplus::message;
43 namespace variant_ns = msg::variant_ns;
44
45 sdbusplus::message::object_path objPath;
46 std::map<std::string, std::map<std::string, msg::variant<std::string>>>
47 interfaces;
48 m.read(objPath, interfaces);
49
50 std::string path(std::move(objPath));
51 std::string filePath;
52 auto purpose = VersionPurpose::Unknown;
53 std::string version;
54
55 for (const auto& intf : interfaces)
56 {
57 if (intf.first == VERSION_IFACE)
58 {
59 for (const auto& property : intf.second)
60 {
61 if (property.first == "Purpose")
62 {
63 // Only process the Host and System images
64 auto value = SVersion::convertVersionPurposeFromString(
65 variant_ns::get<std::string>(property.second));
66
67 if (value == VersionPurpose::Host ||
68 value == VersionPurpose::System)
69 {
70 purpose = value;
71 }
72 }
73 else if (property.first == "Version")
74 {
75 version = variant_ns::get<std::string>(property.second);
76 }
77 }
78 }
79 else if (intf.first == FILEPATH_IFACE)
80 {
81 for (const auto& property : intf.second)
82 {
83 if (property.first == "Path")
84 {
85 filePath = variant_ns::get<std::string>(property.second);
86 }
87 }
88 }
89 }
90 if ((filePath.empty()) || (purpose == VersionPurpose::Unknown))
91 {
92 return;
93 }
94
95 // Version id is the last item in the path
96 auto pos = path.rfind("/");
97 if (pos == std::string::npos)
98 {
99 log<level::ERR>("No version id found in object path",
100 entry("OBJPATH=%s", path.c_str()));
101 return;
102 }
103
104 auto versionId = path.substr(pos + 1);
105
106 if (activations.find(versionId) == activations.end())
107 {
108 // Determine the Activation state by processing the given image dir.
109 auto activationState = server::Activation::Activations::Invalid;
110 AssociationList associations = {};
111 if (validateSquashFSImage(filePath) == 0)
112 {
113 activationState = server::Activation::Activations::Ready;
114 // Create an association to the host inventory item
115 associations.emplace_back(std::make_tuple(
116 ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION,
117 HOST_INVENTORY_PATH));
118 }
119
120 fs::path manifestPath(filePath);
121 manifestPath /= MANIFEST_FILE;
122 std::string extendedVersion =
123 (Version::getValue(
124 manifestPath.string(),
125 std::map<std::string, std::string>{{"extended_version", ""}}))
126 .begin()
127 ->second;
128
129 activations.insert(std::make_pair(
Lei YU9b21efc2019-02-21 15:52:53 +0800130 versionId, std::make_unique<ActivationUbi>(
Lei YUf3ce4332019-02-21 14:09:49 +0800131 bus, path, *this, versionId, extendedVersion,
132 activationState, associations)));
133
134 auto versionPtr = std::make_unique<Version>(
135 bus, path, *this, versionId, version, purpose, filePath,
136 std::bind(&ItemUpdaterUbi::erase, this, std::placeholders::_1));
137 versionPtr->deleteObject =
138 std::make_unique<Delete>(bus, path, *versionPtr);
139 versions.insert(std::make_pair(versionId, std::move(versionPtr)));
140 }
141 return;
142}
143
144void ItemUpdaterUbi::processPNORImage()
145{
146 // Read pnor.toc from folders under /media/
147 // to get Active Software Versions.
148 for (const auto& iter : fs::directory_iterator(MEDIA_DIR))
149 {
150 auto activationState = server::Activation::Activations::Active;
151
152 static const auto PNOR_RO_PREFIX_LEN = strlen(PNOR_RO_PREFIX);
153 static const auto PNOR_RW_PREFIX_LEN = strlen(PNOR_RW_PREFIX);
154
155 // Check if the PNOR_RO_PREFIX is the prefix of the iter.path
156 if (0 ==
157 iter.path().native().compare(0, PNOR_RO_PREFIX_LEN, PNOR_RO_PREFIX))
158 {
159 // The versionId is extracted from the path
160 // for example /media/pnor-ro-2a1022fe.
161 auto id = iter.path().native().substr(PNOR_RO_PREFIX_LEN);
162 auto pnorTOC = iter.path() / PNOR_TOC_FILE;
163 if (!fs::is_regular_file(pnorTOC))
164 {
165 log<level::ERR>("Failed to read pnorTOC.",
166 entry("FILENAME=%s", pnorTOC.c_str()));
167 ItemUpdaterUbi::erase(id);
168 continue;
169 }
170 auto keyValues = Version::getValue(
171 pnorTOC, {{"version", ""}, {"extended_version", ""}});
172 auto& version = keyValues.at("version");
173 if (version.empty())
174 {
175 log<level::ERR>("Failed to read version from pnorTOC",
176 entry("FILENAME=%s", pnorTOC.c_str()));
177 activationState = server::Activation::Activations::Invalid;
178 }
179
180 auto& extendedVersion = keyValues.at("extended_version");
181 if (extendedVersion.empty())
182 {
183 log<level::ERR>("Failed to read extendedVersion from pnorTOC",
184 entry("FILENAME=%s", pnorTOC.c_str()));
185 activationState = server::Activation::Activations::Invalid;
186 }
187
188 auto purpose = server::Version::VersionPurpose::Host;
189 auto path = fs::path(SOFTWARE_OBJPATH) / id;
190 AssociationList associations = {};
191
192 if (activationState == server::Activation::Activations::Active)
193 {
194 // Create an association to the host inventory item
195 associations.emplace_back(std::make_tuple(
196 ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION,
197 HOST_INVENTORY_PATH));
198
199 // Create an active association since this image is active
200 createActiveAssociation(path);
201 }
202
203 // Create Activation instance for this version.
204 activations.insert(
Lei YU9b21efc2019-02-21 15:52:53 +0800205 std::make_pair(id, std::make_unique<ActivationUbi>(
Lei YUf3ce4332019-02-21 14:09:49 +0800206 bus, path, *this, id, extendedVersion,
207 activationState, associations)));
208
209 // If Active, create RedundancyPriority instance for this version.
210 if (activationState == server::Activation::Activations::Active)
211 {
212 uint8_t priority = std::numeric_limits<uint8_t>::max();
213 if (!restoreFromFile(id, priority))
214 {
215 log<level::ERR>("Unable to restore priority from file.",
216 entry("VERSIONID=%s", id.c_str()));
217 }
218 activations.find(id)->second->redundancyPriority =
Lei YU9b21efc2019-02-21 15:52:53 +0800219 std::make_unique<RedundancyPriorityUbi>(
Lei YUf3ce4332019-02-21 14:09:49 +0800220 bus, path, *(activations.find(id)->second), priority);
221 }
222
223 // Create Version instance for this version.
224 auto versionPtr = std::make_unique<Version>(
225 bus, path, *this, id, version, purpose, "",
226 std::bind(&ItemUpdaterUbi::erase, this, std::placeholders::_1));
227 versionPtr->deleteObject =
228 std::make_unique<Delete>(bus, path, *versionPtr);
229 versions.insert(std::make_pair(id, std::move(versionPtr)));
230 }
231 else if (0 == iter.path().native().compare(0, PNOR_RW_PREFIX_LEN,
232 PNOR_RW_PREFIX))
233 {
234 auto id = iter.path().native().substr(PNOR_RW_PREFIX_LEN);
235 auto roDir = PNOR_RO_PREFIX + id;
236 if (!fs::is_directory(roDir))
237 {
238 log<level::ERR>("No corresponding read-only volume found.",
239 entry("DIRNAME=%s", roDir.c_str()));
240 ItemUpdaterUbi::erase(id);
241 }
242 }
243 }
244
245 // Look at the RO symlink to determine if there is a functional image
246 auto id = determineId(PNOR_RO_ACTIVE_PATH);
247 if (!id.empty())
248 {
249 updateFunctionalAssociation(id);
250 }
251 return;
252}
253
254int ItemUpdaterUbi::validateSquashFSImage(const std::string& filePath)
255{
256 auto file = fs::path(filePath) / squashFSImage;
257 if (fs::is_regular_file(file))
258 {
259 return 0;
260 }
261 else
262 {
263 log<level::ERR>("Failed to find the SquashFS image.");
264 return -1;
265 }
266}
267
268void ItemUpdaterUbi::removeReadOnlyPartition(std::string versionId)
269{
270 auto serviceFile = "obmc-flash-bios-ubiumount-ro@" + versionId + ".service";
271
272 // Remove the read-only partitions.
273 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
274 SYSTEMD_INTERFACE, "StartUnit");
275 method.append(serviceFile, "replace");
276 bus.call_noreply(method);
277}
278
279void ItemUpdaterUbi::removeReadWritePartition(std::string versionId)
280{
281 auto serviceFile = "obmc-flash-bios-ubiumount-rw@" + versionId + ".service";
282
283 // Remove the read-write partitions.
284 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
285 SYSTEMD_INTERFACE, "StartUnit");
286 method.append(serviceFile, "replace");
287 bus.call_noreply(method);
288}
289
290void ItemUpdaterUbi::reset()
291{
292 std::vector<uint8_t> mboxdArgs;
293
294 // Suspend mboxd - no args required.
295 auto dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH,
296 MBOXD_INTERFACE, "cmd");
297
298 dbusCall.append(static_cast<uint8_t>(3), mboxdArgs);
299
300 auto responseMsg = bus.call(dbusCall);
301 if (responseMsg.is_method_error())
302 {
303 log<level::ERR>("Error in mboxd suspend call");
304 elog<InternalFailure>();
305 }
306
307 constexpr static auto patchDir = "/usr/local/share/pnor";
308 if (fs::is_directory(patchDir))
309 {
310 for (const auto& iter : fs::directory_iterator(patchDir))
311 {
312 fs::remove_all(iter);
313 }
314 }
315
316 // Clear the read-write partitions.
317 for (const auto& it : activations)
318 {
319 auto rwDir = PNOR_RW_PREFIX + it.first;
320 if (fs::is_directory(rwDir))
321 {
322 for (const auto& iter : fs::directory_iterator(rwDir))
323 {
324 fs::remove_all(iter);
325 }
326 }
327 }
328
329 // Clear the preserved partition.
330 if (fs::is_directory(PNOR_PRSV))
331 {
332 for (const auto& iter : fs::directory_iterator(PNOR_PRSV))
333 {
334 fs::remove_all(iter);
335 }
336 }
337
338 // Resume mboxd with arg 1, indicating that the flash was modified.
339 dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, MBOXD_INTERFACE,
340 "cmd");
341
342 mboxdArgs.push_back(1);
343 dbusCall.append(static_cast<uint8_t>(4), mboxdArgs);
344
345 responseMsg = bus.call(dbusCall);
346 if (responseMsg.is_method_error())
347 {
348 log<level::ERR>("Error in mboxd resume call");
349 elog<InternalFailure>();
350 }
351
352 return;
353}
354
355bool ItemUpdaterUbi::isVersionFunctional(const std::string& versionId)
356{
357 if (!fs::exists(PNOR_RO_ACTIVE_PATH))
358 {
359 return false;
360 }
361
362 fs::path activeRO = fs::read_symlink(PNOR_RO_ACTIVE_PATH);
363
364 if (!fs::is_directory(activeRO))
365 {
366 return false;
367 }
368
369 if (activeRO.string().find(versionId) == std::string::npos)
370 {
371 return false;
372 }
373
374 // active PNOR is the version we're checking
375 return true;
376}
377
378void ItemUpdaterUbi::freePriority(uint8_t value, const std::string& versionId)
379{
380 // TODO openbmc/openbmc#1896 Improve the performance of this function
381 for (const auto& intf : activations)
382 {
383 if (intf.second->redundancyPriority)
384 {
385 if (intf.second->redundancyPriority.get()->priority() == value &&
386 intf.second->versionId != versionId)
387 {
388 intf.second->redundancyPriority.get()->priority(value + 1);
389 }
390 }
391 }
392}
393
Lei YUf3ce4332019-02-21 14:09:49 +0800394bool ItemUpdaterUbi::erase(std::string entryId)
395{
396 if (!ItemUpdater::erase(entryId))
397 {
398 return false;
399 }
400
401 // Remove priority persistence file
402 removeFile(entryId);
403
404 // Removing read-only and read-write partitions
405 removeReadWritePartition(entryId);
406 removeReadOnlyPartition(entryId);
407
408 return true;
409}
410
411void ItemUpdaterUbi::deleteAll()
412{
413 auto chassisOn = isChassisOn();
414
415 for (const auto& activationIt : activations)
416 {
417 if (isVersionFunctional(activationIt.first) && chassisOn)
418 {
419 continue;
420 }
421 else
422 {
423 ItemUpdaterUbi::erase(activationIt.first);
424 }
425 }
426
427 // Remove any remaining pnor-ro- or pnor-rw- volumes that do not match
428 // the current version.
429 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
430 SYSTEMD_INTERFACE, "StartUnit");
431 method.append("obmc-flash-bios-cleanup.service", "replace");
432 bus.call_noreply(method);
433}
434
435// TODO: openbmc/openbmc#1402 Monitor flash usage
436void ItemUpdaterUbi::freeSpace()
437{
438 // Versions with the highest priority in front
439 std::priority_queue<std::pair<int, std::string>,
440 std::vector<std::pair<int, std::string>>,
441 std::less<std::pair<int, std::string>>>
442 versionsPQ;
443
444 std::size_t count = 0;
445 for (const auto& iter : activations)
446 {
447 if (iter.second.get()->activation() ==
448 server::Activation::Activations::Active)
449 {
450 count++;
451 // Don't put the functional version on the queue since we can't
452 // remove the "running" PNOR version if it allows multiple PNORs
453 // But removing functional version if there is only one PNOR.
454 if (ACTIVE_PNOR_MAX_ALLOWED > 1 &&
455 isVersionFunctional(iter.second->versionId))
456 {
457 continue;
458 }
459 versionsPQ.push(std::make_pair(
460 iter.second->redundancyPriority.get()->priority(),
461 iter.second->versionId));
462 }
463 }
464
465 // If the number of PNOR versions is over ACTIVE_PNOR_MAX_ALLOWED -1,
466 // remove the highest priority one(s).
467 while ((count >= ACTIVE_PNOR_MAX_ALLOWED) && (!versionsPQ.empty()))
468 {
469 erase(versionsPQ.top().second);
470 versionsPQ.pop();
471 count--;
472 }
473}
474
Lei YUbee51402019-02-26 11:36:34 +0800475std::string ItemUpdaterUbi::determineId(const std::string& symlinkPath)
Lei YUf3ce4332019-02-21 14:09:49 +0800476{
477 if (!fs::exists(symlinkPath))
478 {
479 return {};
480 }
481
482 auto target = fs::canonical(symlinkPath).string();
483
484 // check to make sure the target really exists
485 if (!fs::is_regular_file(target + "/" + PNOR_TOC_FILE))
486 {
487 return {};
488 }
489 // Get the image <id> from the symlink target
490 // for example /media/ro-2a1022fe
491 static const auto PNOR_RO_PREFIX_LEN = strlen(PNOR_RO_PREFIX);
492 return target.substr(PNOR_RO_PREFIX_LEN);
493}
494
495void GardReset::reset()
496{
497 // The GARD partition is currently misspelled "GUARD." This file path will
498 // need to be updated in the future.
499 auto path = fs::path(PNOR_PRSV_ACTIVE_PATH);
500 path /= "GUARD";
501 std::vector<uint8_t> mboxdArgs;
502
503 auto dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH,
504 MBOXD_INTERFACE, "cmd");
505
506 // Suspend mboxd - no args required.
507 dbusCall.append(static_cast<uint8_t>(3), mboxdArgs);
508
509 auto responseMsg = bus.call(dbusCall);
510 if (responseMsg.is_method_error())
511 {
512 log<level::ERR>("Error in mboxd suspend call");
513 elog<InternalFailure>();
514 }
515
516 if (fs::is_regular_file(path))
517 {
518 fs::remove(path);
519 }
520
521 dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, MBOXD_INTERFACE,
522 "cmd");
523
524 // Resume mboxd with arg 1, indicating that the flash is modified.
525 mboxdArgs.push_back(1);
526 dbusCall.append(static_cast<uint8_t>(4), mboxdArgs);
527
528 responseMsg = bus.call(dbusCall);
529 if (responseMsg.is_method_error())
530 {
531 log<level::ERR>("Error in mboxd resume call");
532 elog<InternalFailure>();
533 }
534}
535
536} // namespace updater
537} // namespace software
538} // namespace openpower