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 
52 std::string CodeUpdate::fetchCurrentBootSide()
53 {
54     return currBootSide;
55 }
56 
57 std::string CodeUpdate::fetchNextBootSide()
58 {
59     return nextBootSide;
60 }
61 
62 int CodeUpdate::setCurrentBootSide(const std::string& currSide)
63 {
64     currBootSide = currSide;
65     return PLDM_SUCCESS;
66 }
67 
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 
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 
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 
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 = bus.new_method_call(mapperService, activeObjPath,
176                                            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 == "xyz.openbmc_project.Software.Activation")
221             {
222                 auto imageInterface = "xyz.openbmc_project.Software.Activation";
223                 auto imageObjPath = path.str.c_str();
224 
225                 try
226                 {
227                     auto propVal = dBusIntf->getDbusPropertyVariant(
228                         imageObjPath, "Activation", imageInterface);
229                     const auto& imageProp = std::get<std::string>(propVal);
230                     if (imageProp == "xyz.openbmc_project.Software."
231                                      "Activation.Activations.Ready" &&
232                         isCodeUpdateInProgress())
233                     {
234                         newImageId = path.str;
235                         if (!imageActivationMatch)
236                         {
237                             imageActivationMatch =
238                                 std::make_unique<sdbusplus::bus::match_t>(
239                                     pldm::utils::DBusHandler::getBus(),
240                                     propertiesChanged(newImageId,
241                                                       "xyz.openbmc_project."
242                                                       "Software.Activation"),
243                                     [this](sdbusplus::message_t& msg) {
244                                 DbusChangedProps props;
245                                 std::string iface;
246                                 msg.read(iface, props);
247                                 const auto itr = props.find("Activation");
248                                 if (itr != props.end())
249                                 {
250                                     PropertyValue value = itr->second;
251                                     auto propVal = std::get<std::string>(value);
252                                     if (propVal ==
253                                         "xyz.openbmc_project.Software."
254                                         "Activation.Activations.Active")
255                                     {
256                                         CodeUpdateState state =
257                                             CodeUpdateState::END;
258                                         setCodeUpdateProgress(false);
259                                         auto sensorId =
260                                             getFirmwareUpdateSensor();
261                                         sendStateSensorEvent(
262                                             sensorId, PLDM_STATE_SENSOR_STATE,
263                                             0, uint8_t(state),
264                                             uint8_t(CodeUpdateState::START));
265                                         newImageId.clear();
266                                     }
267                                     else if (propVal == "xyz.openbmc_project."
268                                                         "Software.Activation."
269                                                         "Activations.Failed" ||
270                                              propVal == "xyz.openbmc_"
271                                                         "project.Software."
272                                                         "Activation."
273                                                         "Activations."
274                                                         "Invalid")
275                                     {
276                                         CodeUpdateState state =
277                                             CodeUpdateState::FAIL;
278                                         setCodeUpdateProgress(false);
279                                         auto sensorId =
280                                             getFirmwareUpdateSensor();
281                                         sendStateSensorEvent(
282                                             sensorId, PLDM_STATE_SENSOR_STATE,
283                                             0, uint8_t(state),
284                                             uint8_t(CodeUpdateState::START));
285                                         newImageId.clear();
286                                     }
287                                 }
288                             });
289                         }
290                         auto rc = setRequestedActivation();
291                         if (rc != PLDM_SUCCESS)
292                         {
293                             error("Could not set Requested Activation");
294                             CodeUpdateState state = CodeUpdateState::FAIL;
295                             setCodeUpdateProgress(false);
296                             auto sensorId = getFirmwareUpdateSensor();
297                             sendStateSensorEvent(
298                                 sensorId, PLDM_STATE_SENSOR_STATE, 0,
299                                 uint8_t(state),
300                                 uint8_t(CodeUpdateState::START));
301                         }
302                         break;
303                     }
304                 }
305                 catch (const sdbusplus::exception_t& e)
306                 {
307                     error(
308                         "Failed to get activation status for interface '{INTERFACE}' and object path '{PATH}', error - {ERROR}",
309                         "ERROR", e, "INTERFACE", imageInterface, "PATH",
310                         imageObjPath);
311                 }
312             }
313         }
314     }));
315 }
316 
317 void CodeUpdate::processPriorityChangeNotification(
318     const DbusChangedProps& chProperties)
319 {
320     static constexpr auto propName = "Priority";
321     const auto it = chProperties.find(propName);
322     if (it == chProperties.end())
323     {
324         return;
325     }
326     uint8_t newVal = std::get<uint8_t>(it->second);
327     nextBootSide = (newVal == 0) ? currBootSide
328                                  : ((currBootSide == Tside) ? Pside : Tside);
329 }
330 
331 void CodeUpdate::setOemPlatformHandler(
332     pldm::responder::oem_platform::Handler* handler)
333 {
334     oemPlatformHandler = handler;
335 }
336 
337 void CodeUpdate::clearDirPath(const std::string& dirPath)
338 {
339     if (!fs::is_directory(dirPath))
340     {
341         error("The directory '{PATH}' does not exist", "PATH", dirPath);
342         return;
343     }
344     for (const auto& iter : fs::directory_iterator(dirPath))
345     {
346         fs::remove_all(iter);
347     }
348 }
349 
350 void CodeUpdate::sendStateSensorEvent(
351     uint16_t sensorId, enum sensor_event_class_states sensorEventClass,
352     uint8_t sensorOffset, uint8_t eventState, uint8_t prevEventState)
353 {
354     pldm::responder::oem_ibm_platform::Handler* oemIbmPlatformHandler =
355         dynamic_cast<pldm::responder::oem_ibm_platform::Handler*>(
356             oemPlatformHandler);
357     oemIbmPlatformHandler->sendStateSensorEvent(
358         sensorId, sensorEventClass, sensorOffset, eventState, prevEventState);
359 }
360 
361 void CodeUpdate::deleteImage()
362 {
363     static constexpr auto UPDATER_SERVICE =
364         "xyz.openbmc_project.Software.BMC.Updater";
365     static constexpr auto SW_OBJ_PATH = "/xyz/openbmc_project/software";
366     static constexpr auto DELETE_INTF =
367         "xyz.openbmc_project.Collection.DeleteAll";
368 
369     auto& bus = dBusIntf->getBus();
370     try
371     {
372         auto method = bus.new_method_call(UPDATER_SERVICE, SW_OBJ_PATH,
373                                           DELETE_INTF, "DeleteAll");
374         bus.call_noreply(method, dbusTimeout);
375     }
376     catch (const std::exception& e)
377     {
378         error(
379             "Failed to delete image at path '{PATH}' and interface '{INTERFACE}', error - {ERROR}",
380             "PATH", SW_OBJ_PATH, "INTERFACE", DELETE_INTF, "ERROR", e);
381         return;
382     }
383 }
384 
385 uint8_t fetchBootSide(uint16_t entityInstance, CodeUpdate* codeUpdate)
386 {
387     uint8_t sensorOpState = tSideNum;
388     if (entityInstance == 0)
389     {
390         auto currSide = codeUpdate->fetchCurrentBootSide();
391         if (currSide == Pside)
392         {
393             sensorOpState = pSideNum;
394         }
395     }
396     else if (entityInstance == 1)
397     {
398         auto nextSide = codeUpdate->fetchNextBootSide();
399         if (nextSide == Pside)
400         {
401             sensorOpState = pSideNum;
402         }
403     }
404     else
405     {
406         sensorOpState = PLDM_SENSOR_UNKNOWN;
407     }
408 
409     return sensorOpState;
410 }
411 
412 int setBootSide(uint16_t entityInstance, uint8_t currState,
413                 const std::vector<set_effecter_state_field>& stateField,
414                 CodeUpdate* codeUpdate)
415 {
416     int rc = PLDM_SUCCESS;
417     auto side = (stateField[currState].effecter_state == pSideNum) ? "P" : "T";
418 
419     if (entityInstance == 0)
420     {
421         rc = codeUpdate->setCurrentBootSide(side);
422     }
423     else if (entityInstance == 1)
424     {
425         rc = codeUpdate->setNextBootSide(side);
426     }
427     else
428     {
429         rc = PLDM_PLATFORM_INVALID_STATE_VALUE;
430     }
431     return rc;
432 }
433 
434 template <typename... T>
435 int executeCmd(const T&... t)
436 {
437     std::stringstream cmd;
438     ((cmd << t << " "), ...) << std::endl;
439     FILE* pipe = popen(cmd.str().c_str(), "r");
440     if (!pipe)
441     {
442         throw std::runtime_error("popen() failed!");
443     }
444     int rc = pclose(pipe);
445     if (WEXITSTATUS(rc))
446     {
447         std::cerr << "Error executing: ";
448         ((std::cerr << " " << t), ...);
449         std::cerr << "\n";
450         return -1;
451     }
452 
453     return 0;
454 }
455 
456 int processCodeUpdateLid(const std::string& filePath)
457 {
458     struct LidHeader
459     {
460         uint16_t magicNumber;
461         uint16_t headerVersion;
462         uint32_t lidNumber;
463         uint32_t lidDate;
464         uint16_t lidTime;
465         uint16_t lidClass;
466         uint32_t lidCrc;
467         uint32_t lidSize;
468         uint32_t headerSize;
469     };
470     LidHeader header;
471 
472     std::ifstream ifs(filePath, std::ios::in | std::ios::binary);
473     if (!ifs)
474     {
475         error("Failed to opening file '{FILE}' ifstream", "PATH", filePath);
476         return PLDM_ERROR;
477     }
478     ifs.seekg(0);
479     ifs.read(reinterpret_cast<char*>(&header), sizeof(header));
480 
481     // File size should be the value of lid size minus the header size
482     auto fileSize = fs::file_size(filePath);
483     fileSize -= htonl(header.headerSize);
484     if (fileSize < htonl(header.lidSize))
485     {
486         // File is not completely written yet
487         ifs.close();
488         return PLDM_SUCCESS;
489     }
490 
491     constexpr auto magicNumber = 0x0222;
492     if (htons(header.magicNumber) != magicNumber)
493     {
494         error("Invalid magic number for file '{PATH}'", "PATH", filePath);
495         ifs.close();
496         return PLDM_ERROR;
497     }
498 
499     fs::create_directories(imageDirPath);
500     fs::create_directories(lidDirPath);
501 
502     constexpr auto bmcClass = 0x2000;
503     if (htons(header.lidClass) == bmcClass)
504     {
505         // Skip the header and concatenate the BMC LIDs into a tar file
506         std::ofstream ofs(tarImagePath,
507                           std::ios::out | std::ios::binary | std::ios::app);
508         ifs.seekg(htonl(header.headerSize));
509         ofs << ifs.rdbuf();
510         ofs.flush();
511         ofs.close();
512     }
513     else
514     {
515         std::stringstream lidFileName;
516         lidFileName << std::hex << htonl(header.lidNumber) << ".lid";
517         auto lidNoHeaderPath = fs::path(lidDirPath) / lidFileName.str();
518         std::ofstream ofs(lidNoHeaderPath,
519                           std::ios::out | std::ios::binary | std::ios::trunc);
520         ifs.seekg(htonl(header.headerSize));
521         ofs << ifs.rdbuf();
522         ofs.flush();
523         ofs.close();
524     }
525 
526     ifs.close();
527     fs::remove(filePath);
528     return PLDM_SUCCESS;
529 }
530 
531 int CodeUpdate::assembleCodeUpdateImage()
532 {
533     pid_t pid = fork();
534 
535     if (pid == 0)
536     {
537         pid_t nextPid = fork();
538         if (nextPid == 0)
539         {
540             // Create the hostfw squashfs image from the LID files without
541             // header
542             auto rc = executeCmd("/usr/sbin/mksquashfs", lidDirPath.c_str(),
543                                  hostfwImagePath.c_str(), "-all-root",
544                                  "-no-recovery");
545             if (rc < 0)
546             {
547                 error("Error occurred during the mksqusquashfs call");
548                 setCodeUpdateProgress(false);
549                 auto sensorId = getFirmwareUpdateSensor();
550                 sendStateSensorEvent(sensorId, PLDM_STATE_SENSOR_STATE, 0,
551                                      uint8_t(CodeUpdateState::FAIL),
552                                      uint8_t(CodeUpdateState::START));
553                 exit(EXIT_FAILURE);
554             }
555 
556             fs::create_directories(updateDirPath);
557 
558             // Extract the BMC tarball content
559             rc = executeCmd("/bin/tar", "-xf", tarImagePath.c_str(), "-C",
560                             updateDirPath);
561             if (rc < 0)
562             {
563                 setCodeUpdateProgress(false);
564                 auto sensorId = getFirmwareUpdateSensor();
565                 sendStateSensorEvent(sensorId, PLDM_STATE_SENSOR_STATE, 0,
566                                      uint8_t(CodeUpdateState::FAIL),
567                                      uint8_t(CodeUpdateState::START));
568                 exit(EXIT_FAILURE);
569             }
570 
571             // Add the hostfw image to the directory where the contents were
572             // extracted
573             fs::copy_file(hostfwImagePath,
574                           fs::path(updateDirPath) / hostfwImageName,
575                           fs::copy_options::overwrite_existing);
576 
577             // Remove the tarball file, then re-generate it with so that the
578             // hostfw image becomes part of the tarball
579             fs::remove(tarImagePath);
580             rc = executeCmd("/bin/tar", "-cf", tarImagePath, ".", "-C",
581                             updateDirPath);
582             if (rc < 0)
583             {
584                 error("Error occurred during the generation of the tarball");
585                 setCodeUpdateProgress(false);
586                 auto sensorId = getFirmwareUpdateSensor();
587                 sendStateSensorEvent(sensorId, PLDM_STATE_SENSOR_STATE, 0,
588                                      uint8_t(CodeUpdateState::FAIL),
589                                      uint8_t(CodeUpdateState::START));
590                 exit(EXIT_FAILURE);
591             }
592 
593             // Copy the tarball to the update directory to trigger the phosphor
594             // software manager to create a version interface
595             fs::copy_file(tarImagePath, updateImagePath,
596                           fs::copy_options::overwrite_existing);
597 
598             // Cleanup
599             fs::remove_all(updateDirPath);
600             fs::remove_all(lidDirPath);
601             fs::remove_all(imageDirPath);
602 
603             exit(EXIT_SUCCESS);
604         }
605         else if (nextPid < 0)
606         {
607             error("Failure occurred during fork, error number - {ERROR_NUM}",
608                   "ERROR_NUM", errno);
609             exit(EXIT_FAILURE);
610         }
611 
612         // Do nothing as parent. When parent exits, child will be reparented
613         // under init and be reaped properly.
614         exit(0);
615     }
616     else if (pid > 0)
617     {
618         int status;
619         if (waitpid(pid, &status, 0) < 0)
620         {
621             error("Error occurred during waitpid, error number - {ERROR_NUM}",
622                   "ERROR_NUM", errno);
623 
624             return PLDM_ERROR;
625         }
626         else if (WEXITSTATUS(status) != 0)
627         {
628             error(
629                 "Failed to execute the assembling of the image, status is {IMG_STATUS}",
630                 "IMG_STATUS", status);
631             return PLDM_ERROR;
632         }
633     }
634     else
635     {
636         error("Error occurred during fork, error number - {ERROR_NUM}}",
637               "ERROR_NUM", errno);
638         return PLDM_ERROR;
639     }
640 
641     return PLDM_SUCCESS;
642 }
643 
644 } // namespace responder
645 } // namespace pldm
646