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