xref: /openbmc/pldm/oem/ibm/libpldmresponder/inband_code_update.cpp (revision 16c2a0a03e5daac77e204eb99e00711490fb6e26)
1  #include "inband_code_update.hpp"
2  
3  #include "libpldmresponder/pdr.hpp"
4  #include "oem_ibm_handler.hpp"
5  #include "xyz/openbmc_project/Common/error.hpp"
6  
7  #include <arpa/inet.h>
8  #include <libpldm/entity.h>
9  
10  #include <phosphor-logging/lg2.hpp>
11  #include <sdbusplus/server.hpp>
12  #include <xyz/openbmc_project/Dump/NewDump/server.hpp>
13  
14  #include <exception>
15  #include <fstream>
16  
17  PHOSPHOR_LOG2_USING;
18  
19  namespace pldm
20  {
21  using namespace utils;
22  
23  namespace responder
24  {
25  using namespace oem_ibm_platform;
26  
27  /** @brief Directory where the lid files without a header are stored */
28  auto lidDirPath = fs::path(LID_STAGING_DIR) / "lid";
29  
30  /** @brief Directory where the image files are stored as they are built */
31  auto imageDirPath = fs::path(LID_STAGING_DIR) / "image";
32  
33  /** @brief Directory where the code update tarball files are stored */
34  auto updateDirPath = fs::path(LID_STAGING_DIR) / "update";
35  
36  /** @brief The file name of the code update tarball */
37  constexpr auto tarImageName = "image.tar";
38  
39  /** @brief The file name of the hostfw image */
40  constexpr auto hostfwImageName = "image-hostfw";
41  
42  /** @brief The path to the code update tarball file */
43  auto tarImagePath = fs::path(imageDirPath) / tarImageName;
44  
45  /** @brief The path to the hostfw image */
46  auto hostfwImagePath = fs::path(imageDirPath) / hostfwImageName;
47  
48  /** @brief The path to the tarball file expected by the phosphor software
49   *         manager */
50  auto updateImagePath = fs::path("/tmp/images") / tarImageName;
51  
fetchCurrentBootSide()52  std::string CodeUpdate::fetchCurrentBootSide()
53  {
54      return currBootSide;
55  }
56  
fetchNextBootSide()57  std::string CodeUpdate::fetchNextBootSide()
58  {
59      return nextBootSide;
60  }
61  
setCurrentBootSide(const std::string & currSide)62  int CodeUpdate::setCurrentBootSide(const std::string& currSide)
63  {
64      currBootSide = currSide;
65      return PLDM_SUCCESS;
66  }
67  
setNextBootSide(const std::string & nextSide)68  int CodeUpdate::setNextBootSide(const std::string& nextSide)
69  {
70      nextBootSide = nextSide;
71      std::string objPath{};
72      if (nextBootSide == currBootSide)
73      {
74          objPath = runningVersion;
75      }
76      else
77      {
78          objPath = nonRunningVersion;
79      }
80      if (objPath.empty())
81      {
82          error("no nonRunningVersion present");
83          return PLDM_PLATFORM_INVALID_STATE_VALUE;
84      }
85  
86      pldm::utils::DBusMapping dbusMapping{objPath, redundancyIntf, "Priority",
87                                           "uint8_t"};
88      uint8_t val = 0;
89      pldm::utils::PropertyValue value = static_cast<uint8_t>(val);
90      try
91      {
92          dBusIntf->setDbusProperty(dbusMapping, value);
93      }
94      catch (const std::exception& e)
95      {
96          error("Failed to set the next boot side to {PATH} , error - {ERROR}",
97                "PATH", objPath, "ERROR", e);
98          return PLDM_ERROR;
99      }
100      return PLDM_SUCCESS;
101  }
102  
setRequestedApplyTime()103  int CodeUpdate::setRequestedApplyTime()
104  {
105      int rc = PLDM_SUCCESS;
106      pldm::utils::PropertyValue value =
107          "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
108      DBusMapping dbusMapping;
109      dbusMapping.objectPath = "/xyz/openbmc_project/software/apply_time";
110      dbusMapping.interface = "xyz.openbmc_project.Software.ApplyTime";
111      dbusMapping.propertyName = "RequestedApplyTime";
112      dbusMapping.propertyType = "string";
113      try
114      {
115          pldm::utils::DBusHandler().setDbusProperty(dbusMapping, value);
116      }
117      catch (const std::exception& e)
118      {
119          error(
120              "Failed to set property '{PROPERTY}' at path '{PATH}' and interface '{INTERFACE}', error - {ERROR}",
121              "PATH", dbusMapping.objectPath, "INTERFACE", dbusMapping.interface,
122              "PROPERTY", dbusMapping.propertyName, "ERROR", e);
123          rc = PLDM_ERROR;
124      }
125      return rc;
126  }
127  
setRequestedActivation()128  int CodeUpdate::setRequestedActivation()
129  {
130      int rc = PLDM_SUCCESS;
131      pldm::utils::PropertyValue value =
132          "xyz.openbmc_project.Software.Activation.RequestedActivations.Active";
133      DBusMapping dbusMapping;
134      dbusMapping.objectPath = newImageId;
135      dbusMapping.interface = "xyz.openbmc_project.Software.Activation";
136      dbusMapping.propertyName = "RequestedActivation";
137      dbusMapping.propertyType = "string";
138      try
139      {
140          pldm::utils::DBusHandler().setDbusProperty(dbusMapping, value);
141      }
142      catch (const std::exception& e)
143      {
144          error(
145              "Failed to set property {PROPERTY} at path '{PATH}' and interface '{INTERFACE}', error - {ERROR}",
146              "PATH", dbusMapping.objectPath, "INTERFACE", dbusMapping.interface,
147              "PROPERTY", dbusMapping.propertyName, "ERROR", e);
148          rc = PLDM_ERROR;
149      }
150      return rc;
151  }
152  
setVersions()153  void CodeUpdate::setVersions()
154  {
155      static constexpr auto mapperService = "xyz.openbmc_project.ObjectMapper";
156      static constexpr auto functionalObjPath =
157          "/xyz/openbmc_project/software/functional";
158      static constexpr auto activeObjPath =
159          "/xyz/openbmc_project/software/active";
160      static constexpr auto propIntf = "org.freedesktop.DBus.Properties";
161  
162      auto& bus = dBusIntf->getBus();
163      try
164      {
165          auto method = bus.new_method_call(mapperService, functionalObjPath,
166                                            propIntf, "Get");
167          method.append("xyz.openbmc_project.Association", "endpoints");
168          std::variant<std::vector<std::string>> paths;
169  
170          auto reply = bus.call(method, dbusTimeout);
171          reply.read(paths);
172  
173          runningVersion = std::get<std::vector<std::string>>(paths)[0];
174  
175          auto method1 =
176              bus.new_method_call(mapperService, activeObjPath, propIntf, "Get");
177          method1.append("xyz.openbmc_project.Association", "endpoints");
178  
179          auto reply1 = bus.call(method1, dbusTimeout);
180          reply1.read(paths);
181          for (const auto& path : std::get<std::vector<std::string>>(paths))
182          {
183              if (path != runningVersion)
184              {
185                  nonRunningVersion = path;
186                  break;
187              }
188          }
189      }
190      catch (const std::exception& e)
191      {
192          error(
193              "Failed to make a d-bus call to Object Mapper Association, error - {ERROR}",
194              "ERROR", e);
195          return;
196      }
197  
198      using namespace sdbusplus::bus::match::rules;
199      captureNextBootSideChange.push_back(
200          std::make_unique<sdbusplus::bus::match_t>(
201              pldm::utils::DBusHandler::getBus(),
202              propertiesChanged(runningVersion, redundancyIntf),
203              [this](sdbusplus::message_t& msg) {
204                  DbusChangedProps props;
205                  std::string iface;
206                  msg.read(iface, props);
207                  processPriorityChangeNotification(props);
208              }));
209      fwUpdateMatcher.push_back(std::make_unique<sdbusplus::bus::match_t>(
210          pldm::utils::DBusHandler::getBus(),
211          "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
212          "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
213          [this](sdbusplus::message_t& msg) {
214              DBusInterfaceAdded interfaces;
215              sdbusplus::message::object_path path;
216              msg.read(path, interfaces);
217  
218              for (auto& interface : interfaces)
219              {
220                  if (interface.first ==
221                      "xyz.openbmc_project.Software.Activation")
222                  {
223                      auto imageInterface =
224                          "xyz.openbmc_project.Software.Activation";
225                      auto imageObjPath = path.str.c_str();
226  
227                      try
228                      {
229                          auto propVal = dBusIntf->getDbusPropertyVariant(
230                              imageObjPath, "Activation", imageInterface);
231                          const auto& imageProp = std::get<std::string>(propVal);
232                          if (imageProp == "xyz.openbmc_project.Software."
233                                           "Activation.Activations.Ready" &&
234                              isCodeUpdateInProgress())
235                          {
236                              newImageId = path.str;
237                              if (!imageActivationMatch)
238                              {
239                                  imageActivationMatch = std::make_unique<
240                                      sdbusplus::bus::match_t>(
241                                      pldm::utils::DBusHandler::getBus(),
242                                      propertiesChanged(newImageId,
243                                                        "xyz.openbmc_project."
244                                                        "Software.Activation"),
245                                      [this](sdbusplus::message_t& msg) {
246                                          DbusChangedProps props;
247                                          std::string iface;
248                                          msg.read(iface, props);
249                                          const auto itr =
250                                              props.find("Activation");
251                                          if (itr != props.end())
252                                          {
253                                              PropertyValue value = itr->second;
254                                              auto propVal =
255                                                  std::get<std::string>(value);
256                                              if (propVal ==
257                                                  "xyz.openbmc_project.Software."
258                                                  "Activation.Activations.Active")
259                                              {
260                                                  CodeUpdateState state =
261                                                      CodeUpdateState::END;
262                                                  setCodeUpdateProgress(false);
263                                                  auto sensorId =
264                                                      getFirmwareUpdateSensor();
265                                                  sendStateSensorEvent(
266                                                      sensorId,
267                                                      PLDM_STATE_SENSOR_STATE, 0,
268                                                      uint8_t(state),
269                                                      uint8_t(CodeUpdateState::
270                                                                  START));
271                                                  newImageId.clear();
272                                              }
273                                              else if (propVal ==
274                                                           "xyz.openbmc_project."
275                                                           "Software.Activation."
276                                                           "Activations.Failed" ||
277                                                       propVal ==
278                                                           "xyz.openbmc_"
279                                                           "project.Software."
280                                                           "Activation."
281                                                           "Activations."
282                                                           "Invalid")
283                                              {
284                                                  CodeUpdateState state =
285                                                      CodeUpdateState::FAIL;
286                                                  setCodeUpdateProgress(false);
287                                                  auto sensorId =
288                                                      getFirmwareUpdateSensor();
289                                                  sendStateSensorEvent(
290                                                      sensorId,
291                                                      PLDM_STATE_SENSOR_STATE, 0,
292                                                      uint8_t(state),
293                                                      uint8_t(CodeUpdateState::
294                                                                  START));
295                                                  newImageId.clear();
296                                              }
297                                          }
298                                      });
299                              }
300                              auto rc = setRequestedActivation();
301                              if (rc != PLDM_SUCCESS)
302                              {
303                                  error("Could not set Requested Activation");
304                                  CodeUpdateState state = CodeUpdateState::FAIL;
305                                  setCodeUpdateProgress(false);
306                                  auto sensorId = getFirmwareUpdateSensor();
307                                  sendStateSensorEvent(
308                                      sensorId, PLDM_STATE_SENSOR_STATE, 0,
309                                      uint8_t(state),
310                                      uint8_t(CodeUpdateState::START));
311                              }
312                              break;
313                          }
314                      }
315                      catch (const sdbusplus::exception_t& e)
316                      {
317                          error(
318                              "Failed to get activation status for interface '{INTERFACE}' and object path '{PATH}', error - {ERROR}",
319                              "ERROR", e, "INTERFACE", imageInterface, "PATH",
320                              imageObjPath);
321                      }
322                  }
323              }
324          }));
325  }
326  
processPriorityChangeNotification(const DbusChangedProps & chProperties)327  void CodeUpdate::processPriorityChangeNotification(
328      const DbusChangedProps& chProperties)
329  {
330      static constexpr auto propName = "Priority";
331      const auto it = chProperties.find(propName);
332      if (it == chProperties.end())
333      {
334          return;
335      }
336      uint8_t newVal = std::get<uint8_t>(it->second);
337      nextBootSide = (newVal == 0) ? currBootSide
338                                   : ((currBootSide == Tside) ? Pside : Tside);
339  }
340  
setOemPlatformHandler(pldm::responder::oem_platform::Handler * handler)341  void CodeUpdate::setOemPlatformHandler(
342      pldm::responder::oem_platform::Handler* handler)
343  {
344      oemPlatformHandler = handler;
345  }
346  
clearDirPath(const std::string & dirPath)347  void CodeUpdate::clearDirPath(const std::string& dirPath)
348  {
349      if (!fs::is_directory(dirPath))
350      {
351          error("The directory '{PATH}' does not exist", "PATH", dirPath);
352          return;
353      }
354      for (const auto& iter : fs::directory_iterator(dirPath))
355      {
356          fs::remove_all(iter);
357      }
358  }
359  
sendStateSensorEvent(uint16_t sensorId,enum sensor_event_class_states sensorEventClass,uint8_t sensorOffset,uint8_t eventState,uint8_t prevEventState)360  void CodeUpdate::sendStateSensorEvent(
361      uint16_t sensorId, enum sensor_event_class_states sensorEventClass,
362      uint8_t sensorOffset, uint8_t eventState, uint8_t prevEventState)
363  {
364      pldm::responder::oem_ibm_platform::Handler* oemIbmPlatformHandler =
365          dynamic_cast<pldm::responder::oem_ibm_platform::Handler*>(
366              oemPlatformHandler);
367      oemIbmPlatformHandler->sendStateSensorEvent(
368          sensorId, sensorEventClass, sensorOffset, eventState, prevEventState);
369  }
370  
deleteImage()371  void CodeUpdate::deleteImage()
372  {
373      static constexpr auto UPDATER_SERVICE =
374          "xyz.openbmc_project.Software.BMC.Updater";
375      static constexpr auto SW_OBJ_PATH = "/xyz/openbmc_project/software";
376      static constexpr auto DELETE_INTF =
377          "xyz.openbmc_project.Collection.DeleteAll";
378  
379      auto& bus = dBusIntf->getBus();
380      try
381      {
382          auto method = bus.new_method_call(UPDATER_SERVICE, SW_OBJ_PATH,
383                                            DELETE_INTF, "DeleteAll");
384          bus.call_noreply(method, dbusTimeout);
385      }
386      catch (const std::exception& e)
387      {
388          error(
389              "Failed to delete image at path '{PATH}' and interface '{INTERFACE}', error - {ERROR}",
390              "PATH", SW_OBJ_PATH, "INTERFACE", DELETE_INTF, "ERROR", e);
391          return;
392      }
393  }
394  
fetchBootSide(uint16_t entityInstance,CodeUpdate * codeUpdate)395  uint8_t fetchBootSide(uint16_t entityInstance, CodeUpdate* codeUpdate)
396  {
397      uint8_t sensorOpState = tSideNum;
398      if (entityInstance == 0)
399      {
400          auto currSide = codeUpdate->fetchCurrentBootSide();
401          if (currSide == Pside)
402          {
403              sensorOpState = pSideNum;
404          }
405      }
406      else if (entityInstance == 1)
407      {
408          auto nextSide = codeUpdate->fetchNextBootSide();
409          if (nextSide == Pside)
410          {
411              sensorOpState = pSideNum;
412          }
413      }
414      else
415      {
416          sensorOpState = PLDM_SENSOR_UNKNOWN;
417      }
418  
419      return sensorOpState;
420  }
421  
setBootSide(uint16_t entityInstance,uint8_t currState,const std::vector<set_effecter_state_field> & stateField,CodeUpdate * codeUpdate)422  int setBootSide(uint16_t entityInstance, uint8_t currState,
423                  const std::vector<set_effecter_state_field>& stateField,
424                  CodeUpdate* codeUpdate)
425  {
426      int rc = PLDM_SUCCESS;
427      auto side = (stateField[currState].effecter_state == pSideNum) ? "P" : "T";
428  
429      if (entityInstance == 0)
430      {
431          rc = codeUpdate->setCurrentBootSide(side);
432      }
433      else if (entityInstance == 1)
434      {
435          rc = codeUpdate->setNextBootSide(side);
436      }
437      else
438      {
439          rc = PLDM_PLATFORM_INVALID_STATE_VALUE;
440      }
441      return rc;
442  }
443  
444  template <typename... T>
executeCmd(const T &...t)445  int executeCmd(const T&... t)
446  {
447      std::stringstream cmd;
448      ((cmd << t << " "), ...) << std::endl;
449      FILE* pipe = popen(cmd.str().c_str(), "r");
450      if (!pipe)
451      {
452          throw std::runtime_error("popen() failed!");
453      }
454      int rc = pclose(pipe);
455      if (WEXITSTATUS(rc))
456      {
457          std::cerr << "Error executing: ";
458          ((std::cerr << " " << t), ...);
459          std::cerr << "\n";
460          return -1;
461      }
462  
463      return 0;
464  }
465  
processCodeUpdateLid(const std::string & filePath)466  int processCodeUpdateLid(const std::string& filePath)
467  {
468      struct LidHeader
469      {
470          uint16_t magicNumber;
471          uint16_t headerVersion;
472          uint32_t lidNumber;
473          uint32_t lidDate;
474          uint16_t lidTime;
475          uint16_t lidClass;
476          uint32_t lidCrc;
477          uint32_t lidSize;
478          uint32_t headerSize;
479      };
480      LidHeader header;
481  
482      std::ifstream ifs(filePath, std::ios::in | std::ios::binary);
483      if (!ifs)
484      {
485          error("Failed to opening file '{FILE}' ifstream", "PATH", filePath);
486          return PLDM_ERROR;
487      }
488      ifs.seekg(0);
489      ifs.read(reinterpret_cast<char*>(&header), sizeof(header));
490  
491      // File size should be the value of lid size minus the header size
492      auto fileSize = fs::file_size(filePath);
493      fileSize -= htonl(header.headerSize);
494      if (fileSize < htonl(header.lidSize))
495      {
496          // File is not completely written yet
497          ifs.close();
498          return PLDM_SUCCESS;
499      }
500  
501      constexpr auto magicNumber = 0x0222;
502      if (htons(header.magicNumber) != magicNumber)
503      {
504          error("Invalid magic number for file '{PATH}'", "PATH", filePath);
505          ifs.close();
506          return PLDM_ERROR;
507      }
508  
509      fs::create_directories(imageDirPath);
510      fs::create_directories(lidDirPath);
511  
512      constexpr auto bmcClass = 0x2000;
513      if (htons(header.lidClass) == bmcClass)
514      {
515          // Skip the header and concatenate the BMC LIDs into a tar file
516          std::ofstream ofs(tarImagePath,
517                            std::ios::out | std::ios::binary | std::ios::app);
518          ifs.seekg(htonl(header.headerSize));
519          ofs << ifs.rdbuf();
520          ofs.flush();
521          ofs.close();
522      }
523      else
524      {
525          std::stringstream lidFileName;
526          lidFileName << std::hex << htonl(header.lidNumber) << ".lid";
527          auto lidNoHeaderPath = fs::path(lidDirPath) / lidFileName.str();
528          std::ofstream ofs(lidNoHeaderPath,
529                            std::ios::out | std::ios::binary | std::ios::trunc);
530          ifs.seekg(htonl(header.headerSize));
531          ofs << ifs.rdbuf();
532          ofs.flush();
533          ofs.close();
534      }
535  
536      ifs.close();
537      fs::remove(filePath);
538      return PLDM_SUCCESS;
539  }
540  
assembleCodeUpdateImage()541  int CodeUpdate::assembleCodeUpdateImage()
542  {
543      pid_t pid = fork();
544  
545      if (pid == 0)
546      {
547          pid_t nextPid = fork();
548          if (nextPid == 0)
549          {
550              // Create the hostfw squashfs image from the LID files without
551              // header
552              auto rc = executeCmd("/usr/sbin/mksquashfs", lidDirPath.c_str(),
553                                   hostfwImagePath.c_str(), "-all-root",
554                                   "-no-recovery");
555              if (rc < 0)
556              {
557                  error("Error occurred during the mksqusquashfs call");
558                  setCodeUpdateProgress(false);
559                  auto sensorId = getFirmwareUpdateSensor();
560                  sendStateSensorEvent(sensorId, PLDM_STATE_SENSOR_STATE, 0,
561                                       uint8_t(CodeUpdateState::FAIL),
562                                       uint8_t(CodeUpdateState::START));
563                  exit(EXIT_FAILURE);
564              }
565  
566              fs::create_directories(updateDirPath);
567  
568              // Extract the BMC tarball content
569              rc = executeCmd("/bin/tar", "-xf", tarImagePath.c_str(), "-C",
570                              updateDirPath);
571              if (rc < 0)
572              {
573                  setCodeUpdateProgress(false);
574                  auto sensorId = getFirmwareUpdateSensor();
575                  sendStateSensorEvent(sensorId, PLDM_STATE_SENSOR_STATE, 0,
576                                       uint8_t(CodeUpdateState::FAIL),
577                                       uint8_t(CodeUpdateState::START));
578                  exit(EXIT_FAILURE);
579              }
580  
581              // Add the hostfw image to the directory where the contents were
582              // extracted
583              fs::copy_file(hostfwImagePath,
584                            fs::path(updateDirPath) / hostfwImageName,
585                            fs::copy_options::overwrite_existing);
586  
587              // Remove the tarball file, then re-generate it with so that the
588              // hostfw image becomes part of the tarball
589              fs::remove(tarImagePath);
590              rc = executeCmd("/bin/tar", "-cf", tarImagePath, ".", "-C",
591                              updateDirPath);
592              if (rc < 0)
593              {
594                  error("Error occurred during the generation of the tarball");
595                  setCodeUpdateProgress(false);
596                  auto sensorId = getFirmwareUpdateSensor();
597                  sendStateSensorEvent(sensorId, PLDM_STATE_SENSOR_STATE, 0,
598                                       uint8_t(CodeUpdateState::FAIL),
599                                       uint8_t(CodeUpdateState::START));
600                  exit(EXIT_FAILURE);
601              }
602  
603              // Copy the tarball to the update directory to trigger the phosphor
604              // software manager to create a version interface
605              fs::copy_file(tarImagePath, updateImagePath,
606                            fs::copy_options::overwrite_existing);
607  
608              // Cleanup
609              fs::remove_all(updateDirPath);
610              fs::remove_all(lidDirPath);
611              fs::remove_all(imageDirPath);
612  
613              exit(EXIT_SUCCESS);
614          }
615          else if (nextPid < 0)
616          {
617              error("Failure occurred during fork, error number - {ERROR_NUM}",
618                    "ERROR_NUM", errno);
619              exit(EXIT_FAILURE);
620          }
621  
622          // Do nothing as parent. When parent exits, child will be reparented
623          // under init and be reaped properly.
624          exit(0);
625      }
626      else if (pid > 0)
627      {
628          int status;
629          if (waitpid(pid, &status, 0) < 0)
630          {
631              error("Error occurred during waitpid, error number - {ERROR_NUM}",
632                    "ERROR_NUM", errno);
633  
634              return PLDM_ERROR;
635          }
636          else if (WEXITSTATUS(status) != 0)
637          {
638              error(
639                  "Failed to execute the assembling of the image, status is {IMG_STATUS}",
640                  "IMG_STATUS", status);
641              return PLDM_ERROR;
642          }
643      }
644      else
645      {
646          error("Error occurred during fork, error number - {ERROR_NUM}}",
647                "ERROR_NUM", errno);
648          return PLDM_ERROR;
649      }
650  
651      return PLDM_SUCCESS;
652  }
653  
654  } // namespace responder
655  } // namespace pldm
656