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