1 #include "config.h"
2 
3 #include "item_updater.hpp"
4 
5 #include "images.hpp"
6 #include "serialize.hpp"
7 #include "version.hpp"
8 #include "xyz/openbmc_project/Software/Version/server.hpp"
9 
10 #include <phosphor-logging/elog-errors.hpp>
11 #include <phosphor-logging/elog.hpp>
12 #include <phosphor-logging/log.hpp>
13 #include <xyz/openbmc_project/Common/error.hpp>
14 #include <xyz/openbmc_project/Software/Image/error.hpp>
15 
16 #include <filesystem>
17 #include <fstream>
18 #include <queue>
19 #include <set>
20 #include <string>
21 #include <thread>
22 
23 namespace phosphor
24 {
25 namespace software
26 {
27 namespace updater
28 {
29 
30 // When you see server:: you know we're referencing our base class
31 namespace server = sdbusplus::xyz::openbmc_project::Software::server;
32 namespace control = sdbusplus::xyz::openbmc_project::Control::server;
33 
34 using namespace phosphor::logging;
35 using namespace sdbusplus::xyz::openbmc_project::Software::Image::Error;
36 using namespace phosphor::software::image;
37 namespace fs = std::filesystem;
38 using NotAllowed = sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed;
39 
40 void ItemUpdater::createActivation(sdbusplus::message::message& msg)
41 {
42 
43     using SVersion = server::Version;
44     using VersionPurpose = SVersion::VersionPurpose;
45     using VersionClass = phosphor::software::manager::Version;
46 
47     sdbusplus::message::object_path objPath;
48     auto purpose = VersionPurpose::Unknown;
49     std::string version;
50     std::map<std::string, std::map<std::string, std::variant<std::string>>>
51         interfaces;
52     msg.read(objPath, interfaces);
53     std::string path(std::move(objPath));
54     std::string filePath;
55 
56     for (const auto& intf : interfaces)
57     {
58         if (intf.first == VERSION_IFACE)
59         {
60             for (const auto& property : intf.second)
61             {
62                 if (property.first == "Purpose")
63                 {
64                     auto value = SVersion::convertVersionPurposeFromString(
65                         std::get<std::string>(property.second));
66                     if (value == VersionPurpose::BMC ||
67 #ifdef HOST_BIOS_UPGRADE
68                         value == VersionPurpose::Host ||
69 #endif
70                         value == VersionPurpose::System)
71                     {
72                         purpose = value;
73                     }
74                 }
75                 else if (property.first == "Version")
76                 {
77                     version = std::get<std::string>(property.second);
78                 }
79             }
80         }
81         else if (intf.first == FILEPATH_IFACE)
82         {
83             for (const auto& property : intf.second)
84             {
85                 if (property.first == "Path")
86                 {
87                     filePath = std::get<std::string>(property.second);
88                 }
89             }
90         }
91     }
92     if (version.empty() || filePath.empty() ||
93         purpose == VersionPurpose::Unknown)
94     {
95         return;
96     }
97 
98     // Version id is the last item in the path
99     auto pos = path.rfind("/");
100     if (pos == std::string::npos)
101     {
102         log<level::ERR>("No version id found in object path",
103                         entry("OBJPATH=%s", path.c_str()));
104         return;
105     }
106 
107     auto versionId = path.substr(pos + 1);
108 
109     if (activations.find(versionId) == activations.end())
110     {
111         // Determine the Activation state by processing the given image dir.
112         auto activationState = server::Activation::Activations::Invalid;
113         ItemUpdater::ActivationStatus result;
114         if (purpose == VersionPurpose::BMC || purpose == VersionPurpose::System)
115             result = ItemUpdater::validateSquashFSImage(filePath);
116         else
117             result = ItemUpdater::ActivationStatus::ready;
118 
119         AssociationList associations = {};
120 
121         if (result == ItemUpdater::ActivationStatus::ready)
122         {
123             activationState = server::Activation::Activations::Ready;
124             // Create an association to the BMC inventory item
125             associations.emplace_back(
126                 std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
127                                 ACTIVATION_REV_ASSOCIATION, bmcInventoryPath));
128         }
129 
130         activations.insert(std::make_pair(
131             versionId,
132             std::make_unique<Activation>(bus, path, *this, versionId,
133                                          activationState, associations)));
134 
135         auto versionPtr = std::make_unique<VersionClass>(
136             bus, path, version, purpose, filePath,
137             std::bind(&ItemUpdater::erase, this, std::placeholders::_1));
138         versionPtr->deleteObject =
139             std::make_unique<phosphor::software::manager::Delete>(bus, path,
140                                                                   *versionPtr);
141         versions.insert(std::make_pair(versionId, std::move(versionPtr)));
142     }
143     return;
144 }
145 
146 void ItemUpdater::processBMCImage()
147 {
148     using VersionClass = phosphor::software::manager::Version;
149 
150     // Check MEDIA_DIR and create if it does not exist
151     try
152     {
153         if (!fs::is_directory(MEDIA_DIR))
154         {
155             fs::create_directory(MEDIA_DIR);
156         }
157     }
158     catch (const fs::filesystem_error& e)
159     {
160         log<level::ERR>("Failed to prepare dir", entry("ERR=%s", e.what()));
161         return;
162     }
163 
164     // Read os-release from /etc/ to get the functional BMC version
165     auto functionalVersion = VersionClass::getBMCVersion(OS_RELEASE_FILE);
166 
167     // Read os-release from folders under /media/ to get
168     // BMC Software Versions.
169     for (const auto& iter : fs::directory_iterator(MEDIA_DIR))
170     {
171         auto activationState = server::Activation::Activations::Active;
172         static const auto BMC_RO_PREFIX_LEN = strlen(BMC_ROFS_PREFIX);
173 
174         // Check if the BMC_RO_PREFIXis the prefix of the iter.path
175         if (0 ==
176             iter.path().native().compare(0, BMC_RO_PREFIX_LEN, BMC_ROFS_PREFIX))
177         {
178             // Get the version to calculate the id
179             fs::path releaseFile(OS_RELEASE_FILE);
180             auto osRelease = iter.path() / releaseFile.relative_path();
181             if (!fs::is_regular_file(osRelease))
182             {
183                 log<level::ERR>(
184                     "Failed to read osRelease",
185                     entry("FILENAME=%s", osRelease.string().c_str()));
186 
187                 // Try to get the version id from the mount directory name and
188                 // call to delete it as this version may be corrupted. Dynamic
189                 // volumes created by the UBI layout for example have the id in
190                 // the mount directory name. The worst that can happen is that
191                 // erase() is called with an non-existent id and returns.
192                 auto id = iter.path().native().substr(BMC_RO_PREFIX_LEN);
193                 ItemUpdater::erase(id);
194 
195                 continue;
196             }
197             auto version = VersionClass::getBMCVersion(osRelease);
198             if (version.empty())
199             {
200                 log<level::ERR>(
201                     "Failed to read version from osRelease",
202                     entry("FILENAME=%s", osRelease.string().c_str()));
203 
204                 // Try to delete the version, same as above if the
205                 // OS_RELEASE_FILE does not exist.
206                 auto id = iter.path().native().substr(BMC_RO_PREFIX_LEN);
207                 ItemUpdater::erase(id);
208 
209                 continue;
210             }
211 
212             auto id = VersionClass::getId(version);
213 
214             // Check if the id has already been added. This can happen if the
215             // BMC partitions / devices were manually flashed with the same
216             // image.
217             if (versions.find(id) != versions.end())
218             {
219                 continue;
220             }
221 
222             auto purpose = server::Version::VersionPurpose::BMC;
223             restorePurpose(id, purpose);
224 
225             auto path = fs::path(SOFTWARE_OBJPATH) / id;
226 
227             // Create functional association if this is the functional
228             // version
229             if (version.compare(functionalVersion) == 0)
230             {
231                 createFunctionalAssociation(path);
232             }
233 
234             AssociationList associations = {};
235 
236             if (activationState == server::Activation::Activations::Active)
237             {
238                 // Create an association to the BMC inventory item
239                 associations.emplace_back(std::make_tuple(
240                     ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION,
241                     bmcInventoryPath));
242 
243                 // Create an active association since this image is active
244                 createActiveAssociation(path);
245             }
246 
247             // All updateable firmware components must expose the updateable
248             // association.
249             createUpdateableAssociation(path);
250 
251             // Create Version instance for this version.
252             auto versionPtr = std::make_unique<VersionClass>(
253                 bus, path, version, purpose, "",
254                 std::bind(&ItemUpdater::erase, this, std::placeholders::_1));
255             auto isVersionFunctional = versionPtr->isFunctional();
256             if (!isVersionFunctional)
257             {
258                 versionPtr->deleteObject =
259                     std::make_unique<phosphor::software::manager::Delete>(
260                         bus, path, *versionPtr);
261             }
262             versions.insert(std::make_pair(id, std::move(versionPtr)));
263 
264             // Create Activation instance for this version.
265             activations.insert(std::make_pair(
266                 id, std::make_unique<Activation>(
267                         bus, path, *this, id, activationState, associations)));
268 
269             // If Active, create RedundancyPriority instance for this
270             // version.
271             if (activationState == server::Activation::Activations::Active)
272             {
273                 uint8_t priority = std::numeric_limits<uint8_t>::max();
274                 if (!restorePriority(id, priority))
275                 {
276                     if (isVersionFunctional)
277                     {
278                         priority = 0;
279                     }
280                     else
281                     {
282                         log<level::ERR>("Unable to restore priority from file.",
283                                         entry("VERSIONID=%s", id.c_str()));
284                     }
285                 }
286                 activations.find(id)->second->redundancyPriority =
287                     std::make_unique<RedundancyPriority>(
288                         bus, path, *(activations.find(id)->second), priority,
289                         false);
290             }
291         }
292     }
293 
294     // If there are no bmc versions mounted under MEDIA_DIR, then read the
295     // /etc/os-release and create rofs-<versionId> under MEDIA_DIR, then call
296     // again processBMCImage() to create the D-Bus interface for it.
297     if (activations.size() == 0)
298     {
299         auto version = VersionClass::getBMCVersion(OS_RELEASE_FILE);
300         auto id = phosphor::software::manager::Version::getId(version);
301         auto versionFileDir = BMC_ROFS_PREFIX + id + "/etc/";
302         try
303         {
304             if (!fs::is_directory(versionFileDir))
305             {
306                 fs::create_directories(versionFileDir);
307             }
308             auto versionFilePath = BMC_ROFS_PREFIX + id + OS_RELEASE_FILE;
309             fs::create_directory_symlink(OS_RELEASE_FILE, versionFilePath);
310             ItemUpdater::processBMCImage();
311         }
312         catch (const std::exception& e)
313         {
314             log<level::ERR>(e.what());
315         }
316     }
317 
318     mirrorUbootToAlt();
319     return;
320 }
321 
322 void ItemUpdater::erase(std::string entryId)
323 {
324     // Find entry in versions map
325     auto it = versions.find(entryId);
326     if (it != versions.end())
327     {
328         if (it->second->isFunctional() && ACTIVE_BMC_MAX_ALLOWED > 1)
329         {
330             log<level::ERR>("Error: Version is currently running on the BMC. "
331                             "Unable to remove.",
332                             entry("VERSIONID=%s", entryId.c_str()));
333             return;
334         }
335     }
336 
337     // First call resetUbootEnvVars() so that the BMC points to a valid image to
338     // boot from. If resetUbootEnvVars() is called after the image is actually
339     // deleted from the BMC flash, there'd be a time window where the BMC would
340     // be pointing to a non-existent image to boot from.
341     // Need to remove the entries from the activations map before that call so
342     // that resetUbootEnvVars() doesn't use the version to be deleted.
343     auto iteratorActivations = activations.find(entryId);
344     if (iteratorActivations == activations.end())
345     {
346         log<level::ERR>("Error: Failed to find version in item updater "
347                         "activations map. Unable to remove.",
348                         entry("VERSIONID=%s", entryId.c_str()));
349     }
350     else
351     {
352         removeAssociations(iteratorActivations->second->path);
353         this->activations.erase(entryId);
354     }
355     ItemUpdater::resetUbootEnvVars();
356 
357     if (it != versions.end())
358     {
359         // Delete ReadOnly partitions if it's not active
360         removeReadOnlyPartition(entryId);
361         removePersistDataDirectory(entryId);
362 
363         // Removing entry in versions map
364         this->versions.erase(entryId);
365     }
366     else
367     {
368         // Delete ReadOnly partitions even if we can't find the version
369         removeReadOnlyPartition(entryId);
370         removePersistDataDirectory(entryId);
371 
372         log<level::ERR>("Error: Failed to find version in item updater "
373                         "versions map. Unable to remove.",
374                         entry("VERSIONID=%s", entryId.c_str()));
375     }
376 
377     helper.clearEntry(entryId);
378 
379     return;
380 }
381 
382 void ItemUpdater::deleteAll()
383 {
384     std::vector<std::string> deletableVersions;
385 
386     for (const auto& versionIt : versions)
387     {
388         if (!versionIt.second->isFunctional())
389         {
390             deletableVersions.push_back(versionIt.first);
391         }
392     }
393 
394     for (const auto& deletableIt : deletableVersions)
395     {
396         ItemUpdater::erase(deletableIt);
397     }
398 
399     helper.cleanup();
400 }
401 
402 ItemUpdater::ActivationStatus
403     ItemUpdater::validateSquashFSImage(const std::string& filePath)
404 {
405     bool invalid = false;
406 
407     for (auto& bmcImage : bmcImages)
408     {
409         fs::path file(filePath);
410         file /= bmcImage;
411         std::ifstream efile(file.c_str());
412         if (efile.good() != 1)
413         {
414             log<level::ERR>("Failed to find the BMC image.",
415                             entry("IMAGE=%s", bmcImage.c_str()));
416             invalid = true;
417         }
418     }
419 
420     if (invalid)
421     {
422         return ItemUpdater::ActivationStatus::invalid;
423     }
424 
425     return ItemUpdater::ActivationStatus::ready;
426 }
427 
428 void ItemUpdater::savePriority(const std::string& versionId, uint8_t value)
429 {
430     storePriority(versionId, value);
431     helper.setEntry(versionId, value);
432 }
433 
434 void ItemUpdater::freePriority(uint8_t value, const std::string& versionId)
435 {
436     std::map<std::string, uint8_t> priorityMap;
437 
438     // Insert the requested version and priority, it may not exist yet.
439     priorityMap.insert(std::make_pair(versionId, value));
440 
441     for (const auto& intf : activations)
442     {
443         if (intf.second->redundancyPriority)
444         {
445             priorityMap.insert(std::make_pair(
446                 intf.first, intf.second->redundancyPriority.get()->priority()));
447         }
448     }
449 
450     // Lambda function to compare 2 priority values, use <= to allow duplicates
451     typedef std::function<bool(std::pair<std::string, uint8_t>,
452                                std::pair<std::string, uint8_t>)>
453         cmpPriority;
454     cmpPriority cmpPriorityFunc =
455         [](std::pair<std::string, uint8_t> priority1,
456            std::pair<std::string, uint8_t> priority2) {
457             return priority1.second <= priority2.second;
458         };
459 
460     // Sort versions by ascending priority
461     std::set<std::pair<std::string, uint8_t>, cmpPriority> prioritySet(
462         priorityMap.begin(), priorityMap.end(), cmpPriorityFunc);
463 
464     auto freePriorityValue = value;
465     for (auto& element : prioritySet)
466     {
467         if (element.first == versionId)
468         {
469             continue;
470         }
471         if (element.second == freePriorityValue)
472         {
473             ++freePriorityValue;
474             auto it = activations.find(element.first);
475             it->second->redundancyPriority.get()->sdbusPriority(
476                 freePriorityValue);
477         }
478     }
479 
480     auto lowestVersion = prioritySet.begin()->first;
481     if (value == prioritySet.begin()->second)
482     {
483         lowestVersion = versionId;
484     }
485     updateUbootEnvVars(lowestVersion);
486 }
487 
488 void ItemUpdater::reset()
489 {
490     constexpr auto setFactoryResetWait = std::chrono::seconds(3);
491     helper.factoryReset();
492 
493     // Need to wait for env variables to complete, otherwise an immediate reboot
494     // will not factory reset.
495     std::this_thread::sleep_for(setFactoryResetWait);
496 
497     log<level::INFO>("BMC factory reset will take effect upon reboot.");
498 }
499 
500 void ItemUpdater::removeReadOnlyPartition(std::string versionId)
501 {
502     helper.removeVersion(versionId);
503 }
504 
505 bool ItemUpdater::fieldModeEnabled(bool value)
506 {
507     // enabling field mode is intended to be one way: false -> true
508     if (value && !control::FieldMode::fieldModeEnabled())
509     {
510         control::FieldMode::fieldModeEnabled(value);
511 
512         auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
513                                           SYSTEMD_INTERFACE, "StartUnit");
514         method.append("obmc-flash-bmc-setenv@fieldmode\\x3dtrue.service",
515                       "replace");
516         bus.call_noreply(method);
517 
518         method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
519                                      SYSTEMD_INTERFACE, "StopUnit");
520         method.append("usr-local.mount", "replace");
521         bus.call_noreply(method);
522 
523         std::vector<std::string> usrLocal = {"usr-local.mount"};
524 
525         method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
526                                      SYSTEMD_INTERFACE, "MaskUnitFiles");
527         method.append(usrLocal, false, true);
528         bus.call_noreply(method);
529     }
530     else if (!value && control::FieldMode::fieldModeEnabled())
531     {
532         elog<NotAllowed>(xyz::openbmc_project::Common::NotAllowed::REASON(
533             "FieldMode is not allowed to be cleared"));
534     }
535 
536     return control::FieldMode::fieldModeEnabled();
537 }
538 
539 void ItemUpdater::restoreFieldModeStatus()
540 {
541     std::ifstream input("/dev/mtd/u-boot-env");
542     std::string envVar;
543     std::getline(input, envVar);
544 
545     if (envVar.find("fieldmode=true") != std::string::npos)
546     {
547         ItemUpdater::fieldModeEnabled(true);
548     }
549 }
550 
551 void ItemUpdater::setBMCInventoryPath()
552 {
553     auto depth = 0;
554     auto mapperCall = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
555                                           MAPPER_INTERFACE, "GetSubTreePaths");
556 
557     mapperCall.append(INVENTORY_PATH);
558     mapperCall.append(depth);
559     std::vector<std::string> filter = {BMC_INVENTORY_INTERFACE};
560     mapperCall.append(filter);
561 
562     try
563     {
564         auto response = bus.call(mapperCall);
565 
566         using ObjectPaths = std::vector<std::string>;
567         ObjectPaths result;
568         response.read(result);
569 
570         if (!result.empty())
571         {
572             bmcInventoryPath = result.front();
573         }
574     }
575     catch (const sdbusplus::exception::SdBusError& e)
576     {
577         log<level::ERR>("Error in mapper GetSubTreePath");
578         return;
579     }
580 
581     return;
582 }
583 
584 void ItemUpdater::createActiveAssociation(const std::string& path)
585 {
586     assocs.emplace_back(
587         std::make_tuple(ACTIVE_FWD_ASSOCIATION, ACTIVE_REV_ASSOCIATION, path));
588     associations(assocs);
589 }
590 
591 void ItemUpdater::createFunctionalAssociation(const std::string& path)
592 {
593     assocs.emplace_back(std::make_tuple(FUNCTIONAL_FWD_ASSOCIATION,
594                                         FUNCTIONAL_REV_ASSOCIATION, path));
595     associations(assocs);
596 }
597 
598 void ItemUpdater::createUpdateableAssociation(const std::string& path)
599 {
600     assocs.emplace_back(std::make_tuple(UPDATEABLE_FWD_ASSOCIATION,
601                                         UPDATEABLE_REV_ASSOCIATION, path));
602     associations(assocs);
603 }
604 
605 void ItemUpdater::removeAssociations(const std::string& path)
606 {
607     for (auto iter = assocs.begin(); iter != assocs.end();)
608     {
609         if ((std::get<2>(*iter)).compare(path) == 0)
610         {
611             iter = assocs.erase(iter);
612             associations(assocs);
613         }
614         else
615         {
616             ++iter;
617         }
618     }
619 }
620 
621 bool ItemUpdater::isLowestPriority(uint8_t value)
622 {
623     for (const auto& intf : activations)
624     {
625         if (intf.second->redundancyPriority)
626         {
627             if (intf.second->redundancyPriority.get()->priority() < value)
628             {
629                 return false;
630             }
631         }
632     }
633     return true;
634 }
635 
636 void ItemUpdater::updateUbootEnvVars(const std::string& versionId)
637 {
638     helper.updateUbootVersionId(versionId);
639 }
640 
641 void ItemUpdater::resetUbootEnvVars()
642 {
643     decltype(activations.begin()->second->redundancyPriority.get()->priority())
644         lowestPriority = std::numeric_limits<uint8_t>::max();
645     decltype(activations.begin()->second->versionId) lowestPriorityVersion;
646     for (const auto& intf : activations)
647     {
648         if (!intf.second->redundancyPriority.get())
649         {
650             // Skip this version if the redundancyPriority is not initialized.
651             continue;
652         }
653 
654         if (intf.second->redundancyPriority.get()->priority() <= lowestPriority)
655         {
656             lowestPriority = intf.second->redundancyPriority.get()->priority();
657             lowestPriorityVersion = intf.second->versionId;
658         }
659     }
660 
661     // Update the U-boot environment variable to point to the lowest priority
662     updateUbootEnvVars(lowestPriorityVersion);
663 }
664 
665 void ItemUpdater::freeSpace(Activation& caller)
666 {
667     //  Versions with the highest priority in front
668     std::priority_queue<std::pair<int, std::string>,
669                         std::vector<std::pair<int, std::string>>,
670                         std::less<std::pair<int, std::string>>>
671         versionsPQ;
672 
673     std::size_t count = 0;
674     for (const auto& iter : activations)
675     {
676         if ((iter.second.get()->activation() ==
677              server::Activation::Activations::Active) ||
678             (iter.second.get()->activation() ==
679              server::Activation::Activations::Failed))
680         {
681             count++;
682             // Don't put the functional version on the queue since we can't
683             // remove the "running" BMC version.
684             // If ACTIVE_BMC_MAX_ALLOWED <= 1, there is only one active BMC,
685             // so remove functional version as well.
686             // Don't delete the the Activation object that called this function.
687             if ((versions.find(iter.second->versionId)
688                      ->second->isFunctional() &&
689                  ACTIVE_BMC_MAX_ALLOWED > 1) ||
690                 (iter.second->versionId == caller.versionId))
691             {
692                 continue;
693             }
694 
695             // Failed activations don't have priority, assign them a large value
696             // for sorting purposes.
697             auto priority = 999;
698             if (iter.second.get()->activation() ==
699                 server::Activation::Activations::Active)
700             {
701                 priority = iter.second->redundancyPriority.get()->priority();
702             }
703 
704             versionsPQ.push(std::make_pair(priority, iter.second->versionId));
705         }
706     }
707 
708     // If the number of BMC versions is over ACTIVE_BMC_MAX_ALLOWED -1,
709     // remove the highest priority one(s).
710     while ((count >= ACTIVE_BMC_MAX_ALLOWED) && (!versionsPQ.empty()))
711     {
712         erase(versionsPQ.top().second);
713         versionsPQ.pop();
714         count--;
715     }
716 }
717 
718 void ItemUpdater::mirrorUbootToAlt()
719 {
720     helper.mirrorAlt();
721 }
722 
723 } // namespace updater
724 } // namespace software
725 } // namespace phosphor
726