1 #include "config.h"
2 
3 #include "item_updater_ubi.hpp"
4 
5 #include "activation_ubi.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 std::unique_ptr<Activation> ItemUpdaterUbi::createActivationObject(
39     const std::string& path, const std::string& versionId,
40     const std::string& extVersion,
41     sdbusplus::xyz::openbmc_project::Software::server::Activation::Activations
42         activationStatus,
43     AssociationList& assocs)
44 {
45     return std::make_unique<ActivationUbi>(
46         bus, path, *this, versionId, extVersion, activationStatus, assocs);
47 }
48 
49 std::unique_ptr<Version> ItemUpdaterUbi::createVersionObject(
50     const std::string& objPath, const std::string& versionId,
51     const std::string& versionString,
52     sdbusplus::xyz::openbmc_project::Software::server::Version::VersionPurpose
53         versionPurpose,
54     const std::string& filePath)
55 {
56     auto version = std::make_unique<Version>(
57         bus, objPath, *this, versionId, versionString, versionPurpose, filePath,
58         std::bind(&ItemUpdaterUbi::erase, this, std::placeholders::_1));
59     version->deleteObject = std::make_unique<Delete>(bus, objPath, *version);
60     return version;
61 }
62 
63 bool ItemUpdaterUbi::validateImage(const std::string& path)
64 {
65     return validateSquashFSImage(path) == 0;
66 }
67 
68 void ItemUpdaterUbi::processPNORImage()
69 {
70     // Read pnor.toc from folders under /media/
71     // to get Active Software Versions.
72     for (const auto& iter : fs::directory_iterator(MEDIA_DIR))
73     {
74         auto activationState = server::Activation::Activations::Active;
75 
76         static const auto PNOR_RO_PREFIX_LEN = strlen(PNOR_RO_PREFIX);
77         static const auto PNOR_RW_PREFIX_LEN = strlen(PNOR_RW_PREFIX);
78 
79         // Check if the PNOR_RO_PREFIX is the prefix of the iter.path
80         if (0 ==
81             iter.path().native().compare(0, PNOR_RO_PREFIX_LEN, PNOR_RO_PREFIX))
82         {
83             // The versionId is extracted from the path
84             // for example /media/pnor-ro-2a1022fe.
85             auto id = iter.path().native().substr(PNOR_RO_PREFIX_LEN);
86             auto pnorTOC = iter.path() / PNOR_TOC_FILE;
87             if (!fs::is_regular_file(pnorTOC))
88             {
89                 log<level::ERR>("Failed to read pnorTOC.",
90                                 entry("FILENAME=%s", pnorTOC.c_str()));
91                 ItemUpdaterUbi::erase(id);
92                 continue;
93             }
94             auto keyValues = Version::getValue(
95                 pnorTOC, {{"version", ""}, {"extended_version", ""}});
96             auto& version = keyValues.at("version");
97             if (version.empty())
98             {
99                 log<level::ERR>("Failed to read version from pnorTOC",
100                                 entry("FILENAME=%s", pnorTOC.c_str()));
101                 activationState = server::Activation::Activations::Invalid;
102             }
103 
104             auto& extendedVersion = keyValues.at("extended_version");
105             if (extendedVersion.empty())
106             {
107                 log<level::ERR>("Failed to read extendedVersion from pnorTOC",
108                                 entry("FILENAME=%s", pnorTOC.c_str()));
109                 activationState = server::Activation::Activations::Invalid;
110             }
111 
112             auto purpose = server::Version::VersionPurpose::Host;
113             auto path = fs::path(SOFTWARE_OBJPATH) / id;
114             AssociationList associations = {};
115 
116             if (activationState == server::Activation::Activations::Active)
117             {
118                 // Create an association to the host inventory item
119                 associations.emplace_back(std::make_tuple(
120                     ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION,
121                     HOST_INVENTORY_PATH));
122 
123                 // Create an active association since this image is active
124                 createActiveAssociation(path);
125             }
126 
127             // Create Activation instance for this version.
128             activations.insert(
129                 std::make_pair(id, std::make_unique<ActivationUbi>(
130                                        bus, path, *this, id, extendedVersion,
131                                        activationState, associations)));
132 
133             // If Active, create RedundancyPriority instance for this version.
134             if (activationState == server::Activation::Activations::Active)
135             {
136                 uint8_t priority = std::numeric_limits<uint8_t>::max();
137                 if (!restoreFromFile(id, priority))
138                 {
139                     log<level::ERR>("Unable to restore priority from file.",
140                                     entry("VERSIONID=%s", id.c_str()));
141                 }
142                 activations.find(id)->second->redundancyPriority =
143                     std::make_unique<RedundancyPriorityUbi>(
144                         bus, path, *(activations.find(id)->second), priority);
145             }
146 
147             // Create Version instance for this version.
148             auto versionPtr = std::make_unique<Version>(
149                 bus, path, *this, id, version, purpose, "",
150                 std::bind(&ItemUpdaterUbi::erase, this, std::placeholders::_1));
151             versionPtr->deleteObject =
152                 std::make_unique<Delete>(bus, path, *versionPtr);
153             versions.insert(std::make_pair(id, std::move(versionPtr)));
154         }
155         else if (0 == iter.path().native().compare(0, PNOR_RW_PREFIX_LEN,
156                                                    PNOR_RW_PREFIX))
157         {
158             auto id = iter.path().native().substr(PNOR_RW_PREFIX_LEN);
159             auto roDir = PNOR_RO_PREFIX + id;
160             if (!fs::is_directory(roDir))
161             {
162                 log<level::ERR>("No corresponding read-only volume found.",
163                                 entry("DIRNAME=%s", roDir.c_str()));
164                 ItemUpdaterUbi::erase(id);
165             }
166         }
167     }
168 
169     // Look at the RO symlink to determine if there is a functional image
170     auto id = determineId(PNOR_RO_ACTIVE_PATH);
171     if (!id.empty())
172     {
173         updateFunctionalAssociation(id);
174     }
175     return;
176 }
177 
178 int ItemUpdaterUbi::validateSquashFSImage(const std::string& filePath)
179 {
180     auto file = fs::path(filePath) / squashFSImage;
181     if (fs::is_regular_file(file))
182     {
183         return 0;
184     }
185     else
186     {
187         log<level::ERR>("Failed to find the SquashFS image.");
188         return -1;
189     }
190 }
191 
192 void ItemUpdaterUbi::removeReadOnlyPartition(std::string versionId)
193 {
194     auto serviceFile = "obmc-flash-bios-ubiumount-ro@" + versionId + ".service";
195 
196     // Remove the read-only partitions.
197     auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
198                                       SYSTEMD_INTERFACE, "StartUnit");
199     method.append(serviceFile, "replace");
200     bus.call_noreply(method);
201 }
202 
203 void ItemUpdaterUbi::removeReadWritePartition(std::string versionId)
204 {
205     auto serviceFile = "obmc-flash-bios-ubiumount-rw@" + versionId + ".service";
206 
207     // Remove the read-write partitions.
208     auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
209                                       SYSTEMD_INTERFACE, "StartUnit");
210     method.append(serviceFile, "replace");
211     bus.call_noreply(method);
212 }
213 
214 void ItemUpdaterUbi::reset()
215 {
216     std::vector<uint8_t> mboxdArgs;
217 
218     // Suspend mboxd - no args required.
219     auto dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH,
220                                         MBOXD_INTERFACE, "cmd");
221 
222     dbusCall.append(static_cast<uint8_t>(3), mboxdArgs);
223 
224     auto responseMsg = bus.call(dbusCall);
225     if (responseMsg.is_method_error())
226     {
227         log<level::ERR>("Error in mboxd suspend call");
228         elog<InternalFailure>();
229     }
230 
231     constexpr static auto patchDir = "/usr/local/share/pnor";
232     if (fs::is_directory(patchDir))
233     {
234         for (const auto& iter : fs::directory_iterator(patchDir))
235         {
236             fs::remove_all(iter);
237         }
238     }
239 
240     // Clear the read-write partitions.
241     for (const auto& it : activations)
242     {
243         auto rwDir = PNOR_RW_PREFIX + it.first;
244         if (fs::is_directory(rwDir))
245         {
246             for (const auto& iter : fs::directory_iterator(rwDir))
247             {
248                 fs::remove_all(iter);
249             }
250         }
251     }
252 
253     // Clear the preserved partition.
254     if (fs::is_directory(PNOR_PRSV))
255     {
256         for (const auto& iter : fs::directory_iterator(PNOR_PRSV))
257         {
258             fs::remove_all(iter);
259         }
260     }
261 
262     // Resume mboxd with arg 1, indicating that the flash was modified.
263     dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, MBOXD_INTERFACE,
264                                    "cmd");
265 
266     mboxdArgs.push_back(1);
267     dbusCall.append(static_cast<uint8_t>(4), mboxdArgs);
268 
269     responseMsg = bus.call(dbusCall);
270     if (responseMsg.is_method_error())
271     {
272         log<level::ERR>("Error in mboxd resume call");
273         elog<InternalFailure>();
274     }
275 
276     return;
277 }
278 
279 bool ItemUpdaterUbi::isVersionFunctional(const std::string& versionId)
280 {
281     if (!fs::exists(PNOR_RO_ACTIVE_PATH))
282     {
283         return false;
284     }
285 
286     fs::path activeRO = fs::read_symlink(PNOR_RO_ACTIVE_PATH);
287 
288     if (!fs::is_directory(activeRO))
289     {
290         return false;
291     }
292 
293     if (activeRO.string().find(versionId) == std::string::npos)
294     {
295         return false;
296     }
297 
298     // active PNOR is the version we're checking
299     return true;
300 }
301 
302 void ItemUpdaterUbi::freePriority(uint8_t value, const std::string& versionId)
303 {
304     // TODO openbmc/openbmc#1896 Improve the performance of this function
305     for (const auto& intf : activations)
306     {
307         if (intf.second->redundancyPriority)
308         {
309             if (intf.second->redundancyPriority.get()->priority() == value &&
310                 intf.second->versionId != versionId)
311             {
312                 intf.second->redundancyPriority.get()->priority(value + 1);
313             }
314         }
315     }
316 }
317 
318 bool ItemUpdaterUbi::erase(std::string entryId)
319 {
320     if (!ItemUpdater::erase(entryId))
321     {
322         return false;
323     }
324 
325     // Remove priority persistence file
326     removeFile(entryId);
327 
328     // Removing read-only and read-write partitions
329     removeReadWritePartition(entryId);
330     removeReadOnlyPartition(entryId);
331 
332     return true;
333 }
334 
335 void ItemUpdaterUbi::deleteAll()
336 {
337     auto chassisOn = isChassisOn();
338 
339     for (const auto& activationIt : activations)
340     {
341         if (isVersionFunctional(activationIt.first) && chassisOn)
342         {
343             continue;
344         }
345         else
346         {
347             ItemUpdaterUbi::erase(activationIt.first);
348         }
349     }
350 
351     // Remove any remaining pnor-ro- or pnor-rw- volumes that do not match
352     // the current version.
353     auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
354                                       SYSTEMD_INTERFACE, "StartUnit");
355     method.append("obmc-flash-bios-cleanup.service", "replace");
356     bus.call_noreply(method);
357 }
358 
359 // TODO: openbmc/openbmc#1402 Monitor flash usage
360 bool ItemUpdaterUbi::freeSpace()
361 {
362     bool isSpaceFreed = false;
363     //  Versions with the highest priority in front
364     std::priority_queue<std::pair<int, std::string>,
365                         std::vector<std::pair<int, std::string>>,
366                         std::less<std::pair<int, std::string>>>
367         versionsPQ;
368 
369     std::size_t count = 0;
370     for (const auto& iter : activations)
371     {
372         if (iter.second.get()->activation() ==
373             server::Activation::Activations::Active)
374         {
375             count++;
376             // Don't put the functional version on the queue since we can't
377             // remove the "running" PNOR version if it allows multiple PNORs
378             // But removing functional version if there is only one PNOR.
379             if (ACTIVE_PNOR_MAX_ALLOWED > 1 &&
380                 isVersionFunctional(iter.second->versionId))
381             {
382                 continue;
383             }
384             versionsPQ.push(std::make_pair(
385                 iter.second->redundancyPriority.get()->priority(),
386                 iter.second->versionId));
387         }
388     }
389 
390     // If the number of PNOR versions is over ACTIVE_PNOR_MAX_ALLOWED -1,
391     // remove the highest priority one(s).
392     while ((count >= ACTIVE_PNOR_MAX_ALLOWED) && (!versionsPQ.empty()))
393     {
394         erase(versionsPQ.top().second);
395         versionsPQ.pop();
396         count--;
397         isSpaceFreed = true;
398     }
399     return isSpaceFreed;
400 }
401 
402 std::string ItemUpdaterUbi::determineId(const std::string& symlinkPath)
403 {
404     if (!fs::exists(symlinkPath))
405     {
406         return {};
407     }
408 
409     auto target = fs::canonical(symlinkPath).string();
410 
411     // check to make sure the target really exists
412     if (!fs::is_regular_file(target + "/" + PNOR_TOC_FILE))
413     {
414         return {};
415     }
416     // Get the image <id> from the symlink target
417     // for example /media/ro-2a1022fe
418     static const auto PNOR_RO_PREFIX_LEN = strlen(PNOR_RO_PREFIX);
419     return target.substr(PNOR_RO_PREFIX_LEN);
420 }
421 
422 void GardResetUbi::reset()
423 {
424     // The GARD partition is currently misspelled "GUARD." This file path will
425     // need to be updated in the future.
426     auto path = fs::path(PNOR_PRSV_ACTIVE_PATH);
427     path /= "GUARD";
428     std::vector<uint8_t> mboxdArgs;
429 
430     auto dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH,
431                                         MBOXD_INTERFACE, "cmd");
432 
433     // Suspend mboxd - no args required.
434     dbusCall.append(static_cast<uint8_t>(3), mboxdArgs);
435 
436     auto responseMsg = bus.call(dbusCall);
437     if (responseMsg.is_method_error())
438     {
439         log<level::ERR>("Error in mboxd suspend call");
440         elog<InternalFailure>();
441     }
442 
443     if (fs::is_regular_file(path))
444     {
445         fs::remove(path);
446     }
447 
448     dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, MBOXD_INTERFACE,
449                                    "cmd");
450 
451     // Resume mboxd with arg 1, indicating that the flash is modified.
452     mboxdArgs.push_back(1);
453     dbusCall.append(static_cast<uint8_t>(4), mboxdArgs);
454 
455     responseMsg = bus.call(dbusCall);
456     if (responseMsg.is_method_error())
457     {
458         log<level::ERR>("Error in mboxd resume call");
459         elog<InternalFailure>();
460     }
461 }
462 
463 } // namespace updater
464 } // namespace software
465 } // namespace openpower
466