xref: /openbmc/openpower-pnor-code-mgmt/ubi/item_updater_ubi.cpp (revision f8e024296d02170b360c894349d900acfd940a4d)
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 =
151                  std::make_unique<Delete>(bus, path, *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