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