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