1 #include <string>
2 #include <experimental/filesystem>
3 #include <fstream>
4 #include <phosphor-logging/elog-errors.hpp>
5 #include <phosphor-logging/log.hpp>
6 #include "xyz/openbmc_project/Common/error.hpp"
7 #include <xyz/openbmc_project/Software/Version/server.hpp>
8 #include "version.hpp"
9 #include "config.h"
10 #include "item_updater.hpp"
11 #include "activation.hpp"
12 #include "serialize.hpp"
13 
14 namespace openpower
15 {
16 namespace software
17 {
18 namespace updater
19 {
20 
21 // When you see server:: you know we're referencing our base class
22 namespace server = sdbusplus::xyz::openbmc_project::Software::server;
23 namespace fs = std::experimental::filesystem;
24 
25 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
26 using namespace phosphor::logging;
27 
28 constexpr auto squashFSImage = "pnor.xz.squashfs";
29 
30 // TODO: Change paths once openbmc/openbmc#1663 is completed.
31 constexpr auto MBOXD_INTERFACE = "org.openbmc.mboxd";
32 constexpr auto MBOXD_PATH = "/org/openbmc/mboxd";
33 
34 void ItemUpdater::createActivation(sdbusplus::message::message& m)
35 {
36     using SVersion = server::Version;
37     using VersionPurpose = SVersion::VersionPurpose;
38     namespace msg = sdbusplus::message;
39     namespace variant_ns = msg::variant_ns;
40 
41     sdbusplus::message::object_path objPath;
42     std::map<std::string,
43              std::map<std::string, msg::variant<std::string>>> interfaces;
44     m.read(objPath, interfaces);
45 
46     std::string path(std::move(objPath));
47     std::string filePath;
48     auto purpose = VersionPurpose::Unknown;
49     std::string version;
50 
51     for (const auto& intf : interfaces)
52     {
53         if (intf.first == VERSION_IFACE)
54         {
55             for (const auto& property : intf.second)
56             {
57                 if (property.first == "Purpose")
58                 {
59                     // Only process the Host and System images
60                     auto value = SVersion::convertVersionPurposeFromString(
61                             variant_ns::get<std::string>(property.second));
62 
63                     if (value == VersionPurpose::Host ||
64                         value == VersionPurpose::System)
65                     {
66                         purpose = value;
67                     }
68                 }
69                 else if (property.first == "Version")
70                 {
71                     version = variant_ns::get<std::string>(property.second);
72                 }
73             }
74         }
75         else if (intf.first == FILEPATH_IFACE)
76         {
77             for (const auto& property : intf.second)
78             {
79                 if (property.first == "Path")
80                 {
81                     filePath = variant_ns::get<std::string>(property.second);
82                 }
83             }
84         }
85     }
86     if ((filePath.empty()) || (purpose == VersionPurpose::Unknown))
87     {
88         return;
89     }
90 
91     // Version id is the last item in the path
92     auto pos = path.rfind("/");
93     if (pos == std::string::npos)
94     {
95         log<level::ERR>("No version id found in object path",
96                         entry("OBJPATH=%s", path));
97         return;
98     }
99 
100     auto versionId = path.substr(pos + 1);
101 
102     if (activations.find(versionId) == activations.end())
103     {
104         // Determine the Activation state by processing the given image dir.
105         auto activationState = server::Activation::Activations::Invalid;
106         AssociationList associations = {};
107         if (ItemUpdater::validateSquashFSImage(filePath) == 0)
108         {
109             activationState = server::Activation::Activations::Ready;
110             // Create an association to the host inventory item
111             associations.emplace_back(std::make_tuple(
112                                               ACTIVATION_FWD_ASSOCIATION,
113                                               ACTIVATION_REV_ASSOCIATION,
114                                               HOST_INVENTORY_PATH));
115         }
116 
117         fs::path manifestPath(filePath);
118         manifestPath /= MANIFEST_FILE;
119         std::string extendedVersion = (Version::getValue(manifestPath.string(),
120                  std::map<std::string, std::string>
121                  {{"extended_version", ""}})).begin()->second;
122 
123         activations.insert(std::make_pair(
124                 versionId,
125                 std::make_unique<Activation>(
126                         bus,
127                         path,
128                         *this,
129                         versionId,
130                         extendedVersion,
131                         activationState,
132                         associations)));
133 
134         auto versionPtr = std::make_unique<Version>(
135                                   bus,
136                                   path,
137                                   *this,
138                                   versionId,
139                                   version,
140                                   purpose,
141                                   filePath,
142                                   std::bind(&ItemUpdater::erase,
143                                             this,
144                                             std::placeholders::_1));
145         versionPtr->deleteObject =
146                 std::make_unique<Delete>(bus, path, *versionPtr);
147         versions.insert(std::make_pair(versionId, std::move(versionPtr)));
148     }
149     return;
150 }
151 
152 void ItemUpdater::processPNORImage()
153 {
154     // Read pnor.toc from folders under /media/
155     // to get Active Software Versions.
156     for (const auto& iter : fs::directory_iterator(MEDIA_DIR))
157     {
158         auto activationState = server::Activation::Activations::Active;
159 
160         static const auto PNOR_RO_PREFIX_LEN = strlen(PNOR_RO_PREFIX);
161         static const auto PNOR_RW_PREFIX_LEN = strlen(PNOR_RW_PREFIX);
162 
163         // Check if the PNOR_RO_PREFIX is the prefix of the iter.path
164         if (0 == iter.path().native().compare(0, PNOR_RO_PREFIX_LEN,
165                                               PNOR_RO_PREFIX))
166         {
167             // The versionId is extracted from the path
168             // for example /media/pnor-ro-2a1022fe.
169             auto id = iter.path().native().substr(PNOR_RO_PREFIX_LEN);
170             auto pnorTOC = iter.path() / PNOR_TOC_FILE;
171             if (!fs::is_regular_file(pnorTOC))
172             {
173                 log<level::ERR>("Failed to read pnorTOC.",
174                                 entry("FILENAME=%s", pnorTOC.string()));
175                 ItemUpdater::erase(id);
176                 continue;
177             }
178             auto keyValues =
179                     Version::getValue(pnorTOC,
180                                       {{ "version", "" },
181                                        { "extended_version", "" } });
182             auto& version = keyValues.at("version");
183             if (version.empty())
184             {
185                 log<level::ERR>("Failed to read version from pnorTOC",
186                                 entry("FILENAME=%s", pnorTOC.string()));
187                 activationState = server::Activation::Activations::Invalid;
188             }
189 
190             auto& extendedVersion = keyValues.at("extended_version");
191             if (extendedVersion.empty())
192             {
193                 log<level::ERR>("Failed to read extendedVersion from pnorTOC",
194                                 entry("FILENAME=%s", pnorTOC.string()));
195                 activationState = server::Activation::Activations::Invalid;
196             }
197 
198             auto purpose = server::Version::VersionPurpose::Host;
199             auto path = fs::path(SOFTWARE_OBJPATH) / id;
200             AssociationList associations = {};
201 
202             if (activationState == server::Activation::Activations::Active)
203             {
204                 // Create an association to the host inventory item
205                 associations.emplace_back(std::make_tuple(
206                                                   ACTIVATION_FWD_ASSOCIATION,
207                                                   ACTIVATION_REV_ASSOCIATION,
208                                                   HOST_INVENTORY_PATH));
209 
210                 // Create an active association since this image is active
211                 createActiveAssociation(path);
212             }
213 
214             // Create Activation instance for this version.
215             activations.insert(std::make_pair(
216                                    id,
217                                    std::make_unique<Activation>(
218                                        bus,
219                                        path,
220                                        *this,
221                                        id,
222                                        extendedVersion,
223                                        activationState,
224                                        associations)));
225 
226             // If Active, create RedundancyPriority instance for this version.
227             if (activationState == server::Activation::Activations::Active)
228             {
229                 uint8_t priority = std::numeric_limits<uint8_t>::max();
230                 if (!restoreFromFile(id, priority))
231                 {
232                     log<level::ERR>("Unable to restore priority from file.",
233                                     entry("VERSIONID=%s", id));
234                 }
235                 activations.find(id)->second->redundancyPriority =
236                          std::make_unique<RedundancyPriority>(
237                              bus,
238                              path,
239                              *(activations.find(id)->second),
240                              priority);
241             }
242 
243             // Create Version instance for this version.
244             auto versionPtr = std::make_unique<Version>(
245                                       bus,
246                                       path,
247                                       *this,
248                                       id,
249                                       version,
250                                       purpose,
251                                       "",
252                                       std::bind(&ItemUpdater::erase,
253                                                 this,
254                                                 std::placeholders::_1));
255             versionPtr->deleteObject =
256                         std::make_unique<Delete>(bus, path, *versionPtr);
257             versions.insert(std::make_pair(
258                                     id,
259                                     std::move(versionPtr)));
260         }
261         else if (0 == iter.path().native().compare(0, PNOR_RW_PREFIX_LEN,
262                                                       PNOR_RW_PREFIX))
263         {
264             auto id = iter.path().native().substr(PNOR_RW_PREFIX_LEN);
265             auto roDir = PNOR_RO_PREFIX + id;
266             if (!fs::is_directory(roDir))
267             {
268                 log<level::ERR>("No corresponding read-only volume found.",
269                                 entry("DIRNAME=%s", roDir));
270                 ItemUpdater::erase(id);
271             }
272         }
273     }
274 
275     // Look at the RO symlink to determine if there is a functional image
276     auto id = determineId(PNOR_RO_ACTIVE_PATH);
277     if (!id.empty())
278     {
279         updateFunctionalAssociation(std::string{SOFTWARE_OBJPATH} + '/' + id);
280     }
281     return;
282 }
283 
284 int ItemUpdater::validateSquashFSImage(const std::string& filePath)
285 {
286     auto file = fs::path(filePath) / squashFSImage;
287     if (fs::is_regular_file(file))
288     {
289         return 0;
290     }
291     else
292     {
293         log<level::ERR>("Failed to find the SquashFS image.");
294         return -1;
295     }
296 }
297 
298 void ItemUpdater::removeReadOnlyPartition(std::string versionId)
299 {
300         auto serviceFile = "obmc-flash-bios-ubiumount-ro@" + versionId +
301                 ".service";
302 
303         // Remove the read-only partitions.
304         auto method = bus.new_method_call(
305                 SYSTEMD_BUSNAME,
306                 SYSTEMD_PATH,
307                 SYSTEMD_INTERFACE,
308                 "StartUnit");
309         method.append(serviceFile, "replace");
310         bus.call_noreply(method);
311 }
312 
313 void ItemUpdater::removeReadWritePartition(std::string versionId)
314 {
315         auto serviceFile = "obmc-flash-bios-ubiumount-rw@" + versionId +
316                 ".service";
317 
318         // Remove the read-write partitions.
319         auto method = bus.new_method_call(
320                 SYSTEMD_BUSNAME,
321                 SYSTEMD_PATH,
322                 SYSTEMD_INTERFACE,
323                 "StartUnit");
324         method.append(serviceFile, "replace");
325         bus.call_noreply(method);
326 }
327 
328 void ItemUpdater::reset()
329 {
330     constexpr static auto patchDir = "/usr/local/share/pnor";
331     if (fs::is_directory(patchDir))
332     {
333         for (const auto& iter : fs::directory_iterator(patchDir))
334         {
335             fs::remove_all(iter);
336         }
337     }
338 
339     for (const auto& it : activations)
340     {
341         auto serviceFile = "obmc-flash-bios-ubiclear@pnor-rw-" + it.first +
342                 ".service";
343 
344         // Clear the read-write partitions.
345         auto method = bus.new_method_call(
346                 SYSTEMD_BUSNAME,
347                 SYSTEMD_PATH,
348                 SYSTEMD_INTERFACE,
349                 "StartUnit");
350         method.append(serviceFile, "replace");
351         auto reply = bus.call(method);
352 
353         if (reply.is_method_error())
354         {
355             log<level::ERR>("Failed to clear read-write partitions",
356                     entry("SERVICE_FILE=%s", serviceFile));
357             elog<InternalFailure>();
358         }
359 
360         removeFile(it.first);
361     }
362     static constexpr auto serviceFile =
363             "obmc-flash-bios-ubiclear@pnor-prsv.service";
364     // Clear the preserved partition.
365     auto method = bus.new_method_call(
366             SYSTEMD_BUSNAME,
367             SYSTEMD_PATH,
368             SYSTEMD_INTERFACE,
369             "StartUnit");
370     method.append(serviceFile, "replace");
371     auto reply = bus.call(method);
372 
373     if (reply.is_method_error())
374     {
375         log<level::ERR>("Failed to clear preserved partition",
376                     entry("SERVICE_FILE=%s", serviceFile));
377         elog<InternalFailure>();
378     }
379 
380     return;
381 }
382 
383 bool ItemUpdater::isVersionFunctional(const std::string& versionId)
384 {
385     if (!fs::exists(PNOR_RO_ACTIVE_PATH))
386     {
387         return false;
388     }
389 
390     fs::path activeRO = fs::read_symlink(PNOR_RO_ACTIVE_PATH);
391 
392     if (!fs::is_directory(activeRO))
393     {
394         return false;
395     }
396 
397     if (activeRO.string().find(versionId) == std::string::npos)
398     {
399         return false;
400     }
401 
402     // active PNOR is the version we're checking
403     return true;
404 }
405 
406 bool ItemUpdater::isChassisOn()
407 {
408     auto mapperCall = bus.new_method_call(
409             MAPPER_BUSNAME,
410             MAPPER_PATH,
411             MAPPER_INTERFACE,
412             "GetObject");
413 
414     mapperCall.append(CHASSIS_STATE_PATH,
415                       std::vector<std::string>({CHASSIS_STATE_OBJ}));
416     auto mapperResponseMsg = bus.call(mapperCall);
417     if (mapperResponseMsg.is_method_error())
418     {
419         log<level::ERR>("Error in Mapper call");
420         elog<InternalFailure>();
421     }
422     using MapperResponseType = std::map<std::string, std::vector<std::string>>;
423     MapperResponseType mapperResponse;
424     mapperResponseMsg.read(mapperResponse);
425     if (mapperResponse.empty())
426     {
427         log<level::ERR>("Invalid Response from mapper");
428         elog<InternalFailure>();
429     }
430 
431     auto method = bus.new_method_call((mapperResponse.begin()->first).c_str(),
432                                       CHASSIS_STATE_PATH,
433                                       SYSTEMD_PROPERTY_INTERFACE,
434                                       "Get");
435     method.append(CHASSIS_STATE_OBJ, "CurrentPowerState");
436     auto response = bus.call(method);
437     if (response.is_method_error())
438     {
439         log<level::ERR>("Error in fetching current Chassis State",
440                         entry("MAPPERRESPONSE=%s",
441                               (mapperResponse.begin()->first).c_str()));
442         elog<InternalFailure>();
443     }
444     sdbusplus::message::variant<std::string> currentChassisState;
445     response.read(currentChassisState);
446     auto strParam =
447         sdbusplus::message::variant_ns::get<std::string>(currentChassisState);
448     return (strParam != CHASSIS_STATE_OFF);
449 }
450 
451 void ItemUpdater::freePriority(uint8_t value, const std::string& versionId)
452 {
453     //TODO openbmc/openbmc#1896 Improve the performance of this function
454     for (const auto& intf : activations)
455     {
456         if (intf.second->redundancyPriority)
457         {
458             if (intf.second->redundancyPriority.get()->priority() == value &&
459                 intf.second->versionId != versionId)
460             {
461                 intf.second->redundancyPriority.get()->priority(value + 1);
462             }
463         }
464     }
465 }
466 
467 bool ItemUpdater::isLowestPriority(uint8_t value)
468 {
469     for (const auto& intf : activations)
470     {
471         if (intf.second->redundancyPriority)
472         {
473             if (intf.second->redundancyPriority.get()->priority() < value)
474             {
475                 return false;
476             }
477         }
478     }
479     return true;
480 }
481 
482 void ItemUpdater::erase(std::string entryId)
483 {
484     if (isVersionFunctional(entryId) && isChassisOn()) {
485         log<level::ERR>(("Error: Version " + entryId + \
486                          " is currently active and running on the host." \
487                          " Unable to remove.").c_str());
488         return;
489     }
490     // Remove priority persistence file
491     removeFile(entryId);
492 
493     // Removing read-only and read-write partitions
494     removeReadWritePartition(entryId);
495     removeReadOnlyPartition(entryId);
496 
497     // Removing entry in versions map
498     auto it = versions.find(entryId);
499     if (it == versions.end())
500     {
501         log<level::ERR>(("Error: Failed to find version " + entryId + \
502                          " in item updater versions map." \
503                          " Unable to remove.").c_str());
504     }
505     else
506     {
507         versions.erase(entryId);
508     }
509 
510     // Removing entry in activations map
511     auto ita = activations.find(entryId);
512     if (ita == activations.end())
513     {
514         log<level::ERR>(("Error: Failed to find version " + entryId + \
515                          " in item updater activations map." \
516                          " Unable to remove.").c_str());
517     }
518     else
519     {
520         activations.erase(entryId);
521     }
522     return;
523 }
524 
525 void ItemUpdater::deleteAll()
526 {
527     for (const auto& activationIt : activations)
528     {
529         if (!isVersionFunctional(activationIt.first))
530         {
531             ItemUpdater::erase(activationIt.first);
532         }
533     }
534 
535     // Remove any remaining pnor-ro- or pnor-rw- volumes that do not match
536     // the current version.
537     auto method = bus.new_method_call(
538             SYSTEMD_BUSNAME,
539             SYSTEMD_PATH,
540             SYSTEMD_INTERFACE,
541             "StartUnit");
542     method.append("obmc-flash-bios-cleanup.service", "replace");
543     bus.call_noreply(method);
544 }
545 
546 // TODO: openbmc/openbmc#1402 Monitor flash usage
547 void ItemUpdater::freeSpace()
548 {
549     std::size_t count = 0;
550     decltype(activations.begin()->second->redundancyPriority.get()->priority())
551             highestPriority = 0;
552     decltype(activations.begin()->second->versionId) highestPriorityVersion;
553     for (const auto& iter : activations)
554     {
555         if (iter.second.get()->activation() == server::Activation::Activations::Active)
556         {
557             count++;
558             if (isVersionFunctional(iter.second->versionId))
559             {
560                 continue;
561             }
562             if (iter.second->redundancyPriority.get()->priority() >= highestPriority)
563             {
564                 highestPriority = iter.second->redundancyPriority.get()->priority();
565                 highestPriorityVersion = iter.second->versionId;
566             }
567         }
568     }
569     // Remove the pnor version with highest priority since the PNOR
570     // can't hold more than 2 versions.
571     if (count >= ACTIVE_PNOR_MAX_ALLOWED)
572     {
573         erase(highestPriorityVersion);
574     }
575 }
576 
577 void ItemUpdater::createActiveAssociation(const std::string& path)
578 {
579     assocs.emplace_back(std::make_tuple(ACTIVE_FWD_ASSOCIATION,
580                                         ACTIVE_REV_ASSOCIATION,
581                                         path));
582     associations(assocs);
583 }
584 
585 void ItemUpdater::updateFunctionalAssociation(const std::string& path)
586 {
587     // remove all functional associations
588     for (auto iter = assocs.begin(); iter != assocs.end();)
589     {
590         if ((std::get<0>(*iter)).compare(FUNCTIONAL_FWD_ASSOCIATION) == 0)
591         {
592             iter = assocs.erase(iter);
593         }
594         else
595         {
596             ++iter;
597         }
598     }
599     assocs.emplace_back(std::make_tuple(FUNCTIONAL_FWD_ASSOCIATION,
600                                         FUNCTIONAL_REV_ASSOCIATION,
601                                         path));
602     associations(assocs);
603 }
604 
605 void ItemUpdater::removeActiveAssociation(const std::string& path)
606 {
607     for (auto iter = assocs.begin(); iter != assocs.end();)
608     {
609         if ((std::get<0>(*iter)).compare(ACTIVE_FWD_ASSOCIATION) == 0 &&
610             (std::get<2>(*iter)).compare(path) == 0)
611         {
612             iter = assocs.erase(iter);
613             associations(assocs);
614         }
615         else
616         {
617             ++iter;
618         }
619     }
620 }
621 
622 std::string ItemUpdater::determineId(const std::string& symlinkPath)
623 {
624     if (!fs::exists(symlinkPath))
625     {
626         return {};
627     }
628 
629     auto target = fs::canonical(symlinkPath).string();
630 
631     // check to make sure the target really exists
632     if (!fs::is_regular_file(target + "/" + PNOR_TOC_FILE))
633     {
634         return {};
635     }
636     // Get the image <id> from the symlink target
637     // for example /media/ro-2a1022fe
638     static const auto PNOR_RO_PREFIX_LEN = strlen(PNOR_RO_PREFIX);
639     return target.substr(PNOR_RO_PREFIX_LEN);
640 }
641 
642 void GardReset::reset()
643 {
644     // The GARD partition is currently misspelled "GUARD." This file path will
645     // need to be updated in the future.
646     auto path = fs::path(PNOR_PRSV_ACTIVE_PATH);
647     path /= "GUARD";
648     std::vector<uint8_t> mboxdArgs;
649 
650     auto dbusCall = bus.new_method_call(
651             MBOXD_INTERFACE,
652             MBOXD_PATH,
653             MBOXD_INTERFACE,
654             "cmd");
655 
656     // Suspend mboxd - no args required.
657     dbusCall.append(static_cast<uint8_t>(3), mboxdArgs);
658 
659     auto responseMsg = bus.call(dbusCall);
660     if (responseMsg.is_method_error())
661     {
662         log<level::ERR>("Error in mboxd suspend call");
663         elog<InternalFailure>();
664     }
665 
666     if (fs::is_regular_file(path))
667     {
668         fs::remove(path);
669     }
670 
671     dbusCall = bus.new_method_call(
672             MBOXD_INTERFACE,
673             MBOXD_PATH,
674             MBOXD_INTERFACE,
675             "cmd");
676 
677     // Resume mboxd with arg 1, indicating that the flash is modified.
678     mboxdArgs.push_back(1);
679     dbusCall.append(static_cast<uint8_t>(4), mboxdArgs);
680 
681     responseMsg = bus.call(dbusCall);
682     if (responseMsg.is_method_error())
683     {
684         log<level::ERR>("Error in mboxd resume call");
685         elog<InternalFailure>();
686     }
687 }
688 
689 } // namespace updater
690 } // namespace software
691 } // namespace openpower
692