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