1 #include "config.h"
2 
3 #include "item_updater_ubi.hpp"
4 
5 #include "activation.hpp"
6 #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 
18 namespace openpower
19 {
20 namespace software
21 {
22 namespace updater
23 {
24 
25 // When you see server:: you know we're referencing our base class
26 namespace server = sdbusplus::xyz::openbmc_project::Software::server;
27 namespace fs = std::experimental::filesystem;
28 
29 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
30 using namespace phosphor::logging;
31 
32 constexpr auto squashFSImage = "pnor.xz.squashfs";
33 
34 // TODO: Change paths once openbmc/openbmc#1663 is completed.
35 constexpr auto MBOXD_INTERFACE = "org.openbmc.mboxd";
36 constexpr auto MBOXD_PATH = "/org/openbmc/mboxd";
37 
38 void 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(
130             versionId, std::make_unique<Activation>(
131                            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 
144 void 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(
205                 std::make_pair(id, std::make_unique<Activation>(
206                                        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 =
219                     std::make_unique<RedundancyPriority>(
220                         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 
254 int 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 
268 void 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 
279 void 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 
290 void 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 
355 bool 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 
378 void 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 
394 bool ItemUpdaterUbi::isLowestPriority(uint8_t value)
395 {
396     for (const auto& intf : activations)
397     {
398         if (intf.second->redundancyPriority)
399         {
400             if (intf.second->redundancyPriority.get()->priority() < value)
401             {
402                 return false;
403             }
404         }
405     }
406     return true;
407 }
408 
409 bool ItemUpdaterUbi::erase(std::string entryId)
410 {
411     if (!ItemUpdater::erase(entryId))
412     {
413         return false;
414     }
415 
416     // Remove priority persistence file
417     removeFile(entryId);
418 
419     // Removing read-only and read-write partitions
420     removeReadWritePartition(entryId);
421     removeReadOnlyPartition(entryId);
422 
423     return true;
424 }
425 
426 void ItemUpdaterUbi::deleteAll()
427 {
428     auto chassisOn = isChassisOn();
429 
430     for (const auto& activationIt : activations)
431     {
432         if (isVersionFunctional(activationIt.first) && chassisOn)
433         {
434             continue;
435         }
436         else
437         {
438             ItemUpdaterUbi::erase(activationIt.first);
439         }
440     }
441 
442     // Remove any remaining pnor-ro- or pnor-rw- volumes that do not match
443     // the current version.
444     auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
445                                       SYSTEMD_INTERFACE, "StartUnit");
446     method.append("obmc-flash-bios-cleanup.service", "replace");
447     bus.call_noreply(method);
448 }
449 
450 // TODO: openbmc/openbmc#1402 Monitor flash usage
451 void ItemUpdaterUbi::freeSpace()
452 {
453     //  Versions with the highest priority in front
454     std::priority_queue<std::pair<int, std::string>,
455                         std::vector<std::pair<int, std::string>>,
456                         std::less<std::pair<int, std::string>>>
457         versionsPQ;
458 
459     std::size_t count = 0;
460     for (const auto& iter : activations)
461     {
462         if (iter.second.get()->activation() ==
463             server::Activation::Activations::Active)
464         {
465             count++;
466             // Don't put the functional version on the queue since we can't
467             // remove the "running" PNOR version if it allows multiple PNORs
468             // But removing functional version if there is only one PNOR.
469             if (ACTIVE_PNOR_MAX_ALLOWED > 1 &&
470                 isVersionFunctional(iter.second->versionId))
471             {
472                 continue;
473             }
474             versionsPQ.push(std::make_pair(
475                 iter.second->redundancyPriority.get()->priority(),
476                 iter.second->versionId));
477         }
478     }
479 
480     // If the number of PNOR versions is over ACTIVE_PNOR_MAX_ALLOWED -1,
481     // remove the highest priority one(s).
482     while ((count >= ACTIVE_PNOR_MAX_ALLOWED) && (!versionsPQ.empty()))
483     {
484         erase(versionsPQ.top().second);
485         versionsPQ.pop();
486         count--;
487     }
488 }
489 
490 std::string ItemUpdater::determineId(const std::string& symlinkPath)
491 {
492     if (!fs::exists(symlinkPath))
493     {
494         return {};
495     }
496 
497     auto target = fs::canonical(symlinkPath).string();
498 
499     // check to make sure the target really exists
500     if (!fs::is_regular_file(target + "/" + PNOR_TOC_FILE))
501     {
502         return {};
503     }
504     // Get the image <id> from the symlink target
505     // for example /media/ro-2a1022fe
506     static const auto PNOR_RO_PREFIX_LEN = strlen(PNOR_RO_PREFIX);
507     return target.substr(PNOR_RO_PREFIX_LEN);
508 }
509 
510 void GardReset::reset()
511 {
512     // The GARD partition is currently misspelled "GUARD." This file path will
513     // need to be updated in the future.
514     auto path = fs::path(PNOR_PRSV_ACTIVE_PATH);
515     path /= "GUARD";
516     std::vector<uint8_t> mboxdArgs;
517 
518     auto dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH,
519                                         MBOXD_INTERFACE, "cmd");
520 
521     // Suspend mboxd - no args required.
522     dbusCall.append(static_cast<uint8_t>(3), mboxdArgs);
523 
524     auto responseMsg = bus.call(dbusCall);
525     if (responseMsg.is_method_error())
526     {
527         log<level::ERR>("Error in mboxd suspend call");
528         elog<InternalFailure>();
529     }
530 
531     if (fs::is_regular_file(path))
532     {
533         fs::remove(path);
534     }
535 
536     dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, MBOXD_INTERFACE,
537                                    "cmd");
538 
539     // Resume mboxd with arg 1, indicating that the flash is modified.
540     mboxdArgs.push_back(1);
541     dbusCall.append(static_cast<uint8_t>(4), mboxdArgs);
542 
543     responseMsg = bus.call(dbusCall);
544     if (responseMsg.is_method_error())
545     {
546         log<level::ERR>("Error in mboxd resume call");
547         elog<InternalFailure>();
548     }
549 }
550 
551 } // namespace updater
552 } // namespace software
553 } // namespace openpower
554