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