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