1 #include "inband_code_update.hpp"
2 
3 #include "libpldm/entity.h"
4 
5 #include "libpldmresponder/pdr.hpp"
6 #include "oem_ibm_handler.hpp"
7 #include "xyz/openbmc_project/Common/error.hpp"
8 
9 #include <arpa/inet.h>
10 
11 #include <sdbusplus/server.hpp>
12 #include <xyz/openbmc_project/Dump/NewDump/server.hpp>
13 
14 #include <exception>
15 #include <fstream>
16 namespace pldm
17 {
18 using namespace utils;
19 
20 namespace responder
21 {
22 using namespace oem_ibm_platform;
23 
24 /** @brief Directory where the lid files without a header are stored */
25 auto lidDirPath = fs::path(LID_STAGING_DIR) / "lid";
26 
27 /** @brief Directory where the image files are stored as they are built */
28 auto imageDirPath = fs::path(LID_STAGING_DIR) / "image";
29 
30 /** @brief Directory where the code update tarball files are stored */
31 auto updateDirPath = fs::path(LID_STAGING_DIR) / "update";
32 
33 /** @brief The file name of the code update tarball */
34 constexpr auto tarImageName = "image.tar";
35 
36 /** @brief The file name of the hostfw image */
37 constexpr auto hostfwImageName = "image-hostfw";
38 
39 /** @brief The path to the code update tarball file */
40 auto tarImagePath = fs::path(imageDirPath) / tarImageName;
41 
42 /** @brief The path to the hostfw image */
43 auto hostfwImagePath = fs::path(imageDirPath) / hostfwImageName;
44 
45 /** @brief The path to the tarball file expected by the phosphor software
46  *         manager */
47 auto updateImagePath = fs::path("/tmp/images") / tarImageName;
48 
49 std::string CodeUpdate::fetchCurrentBootSide()
50 {
51     return currBootSide;
52 }
53 
54 std::string CodeUpdate::fetchNextBootSide()
55 {
56     return nextBootSide;
57 }
58 
59 int CodeUpdate::setCurrentBootSide(const std::string& currSide)
60 {
61     currBootSide = currSide;
62     return PLDM_SUCCESS;
63 }
64 
65 int CodeUpdate::setNextBootSide(const std::string& nextSide)
66 {
67     nextBootSide = nextSide;
68     std::string objPath{};
69     if (nextBootSide == currBootSide)
70     {
71         objPath = runningVersion;
72     }
73     else
74     {
75         objPath = nonRunningVersion;
76     }
77     if (objPath.empty())
78     {
79         std::cerr << "no nonRunningVersion present \n";
80         return PLDM_PLATFORM_INVALID_STATE_VALUE;
81     }
82 
83     pldm::utils::DBusMapping dbusMapping{objPath, redundancyIntf, "Priority",
84                                          "uint8_t"};
85     uint8_t val = 0;
86     pldm::utils::PropertyValue value = static_cast<uint8_t>(val);
87     try
88     {
89         dBusIntf->setDbusProperty(dbusMapping, value);
90     }
91     catch (const std::exception& e)
92     {
93         std::cerr << "failed to set the next boot side to " << objPath.c_str()
94                   << " ERROR=" << e.what() << "\n";
95         return PLDM_ERROR;
96     }
97     return PLDM_SUCCESS;
98 }
99 
100 int CodeUpdate::setRequestedApplyTime()
101 {
102     int rc = PLDM_SUCCESS;
103     pldm::utils::PropertyValue value =
104         "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
105     DBusMapping dbusMapping;
106     dbusMapping.objectPath = "/xyz/openbmc_project/software/apply_time";
107     dbusMapping.interface = "xyz.openbmc_project.Software.ApplyTime";
108     dbusMapping.propertyName = "RequestedApplyTime";
109     dbusMapping.propertyType = "string";
110     try
111     {
112         pldm::utils::DBusHandler().setDbusProperty(dbusMapping, value);
113     }
114     catch (const std::exception& e)
115     {
116         std::cerr << "Failed To set RequestedApplyTime property "
117                   << "ERROR=" << e.what() << std::endl;
118         rc = PLDM_ERROR;
119     }
120     return rc;
121 }
122 
123 int CodeUpdate::setRequestedActivation()
124 {
125     int rc = PLDM_SUCCESS;
126     pldm::utils::PropertyValue value =
127         "xyz.openbmc_project.Software.Activation.RequestedActivations.Active";
128     DBusMapping dbusMapping;
129     dbusMapping.objectPath = newImageId;
130     dbusMapping.interface = "xyz.openbmc_project.Software.Activation";
131     dbusMapping.propertyName = "RequestedActivation";
132     dbusMapping.propertyType = "string";
133     try
134     {
135         pldm::utils::DBusHandler().setDbusProperty(dbusMapping, value);
136     }
137     catch (const std::exception& e)
138     {
139         std::cerr << "Failed To set RequestedActivation property"
140                   << "ERROR=" << e.what() << std::endl;
141         rc = PLDM_ERROR;
142     }
143     return rc;
144 }
145 
146 void CodeUpdate::setVersions()
147 {
148     static constexpr auto mapperService = "xyz.openbmc_project.ObjectMapper";
149     static constexpr auto functionalObjPath =
150         "/xyz/openbmc_project/software/functional";
151     static constexpr auto activeObjPath =
152         "/xyz/openbmc_project/software/active";
153     static constexpr auto propIntf = "org.freedesktop.DBus.Properties";
154 
155     auto& bus = dBusIntf->getBus();
156     try
157     {
158         auto method = bus.new_method_call(mapperService, functionalObjPath,
159                                           propIntf, "Get");
160         method.append("xyz.openbmc_project.Association", "endpoints");
161         std::variant<std::vector<std::string>> paths;
162 
163         auto reply = bus.call(method);
164         reply.read(paths);
165 
166         runningVersion = std::get<std::vector<std::string>>(paths)[0];
167 
168         auto method1 =
169             bus.new_method_call(mapperService, activeObjPath, propIntf, "Get");
170         method1.append("xyz.openbmc_project.Association", "endpoints");
171 
172         auto reply1 = bus.call(method1);
173         reply1.read(paths);
174         for (const auto& path : std::get<std::vector<std::string>>(paths))
175         {
176             if (path != runningVersion)
177             {
178                 nonRunningVersion = path;
179                 break;
180             }
181         }
182     }
183     catch (const std::exception& e)
184     {
185         std::cerr << "failed to make a d-bus call to Object Mapper "
186                      "Association, ERROR="
187                   << e.what() << "\n";
188         return;
189     }
190 
191     using namespace sdbusplus::bus::match::rules;
192     captureNextBootSideChange.push_back(
193         std::make_unique<sdbusplus::bus::match_t>(
194             pldm::utils::DBusHandler::getBus(),
195             propertiesChanged(runningVersion, redundancyIntf),
196             [this](sdbusplus::message_t& msg) {
197                 DbusChangedProps props;
198                 std::string iface;
199                 msg.read(iface, props);
200                 processPriorityChangeNotification(props);
201             }));
202     fwUpdateMatcher.push_back(std::make_unique<sdbusplus::bus::match_t>(
203         pldm::utils::DBusHandler::getBus(),
204         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
205         "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
206         [this](sdbusplus::message_t& msg) {
207             DBusInterfaceAdded interfaces;
208             sdbusplus::message::object_path path;
209             msg.read(path, interfaces);
210 
211             for (auto& interface : interfaces)
212             {
213                 if (interface.first ==
214                     "xyz.openbmc_project.Software.Activation")
215                 {
216                     auto imageInterface =
217                         "xyz.openbmc_project.Software.Activation";
218                     auto imageObjPath = path.str.c_str();
219 
220                     try
221                     {
222                         auto propVal = dBusIntf->getDbusPropertyVariant(
223                             imageObjPath, "Activation", imageInterface);
224                         const auto& imageProp = std::get<std::string>(propVal);
225                         if (imageProp == "xyz.openbmc_project.Software."
226                                          "Activation.Activations.Ready" &&
227                             isCodeUpdateInProgress())
228                         {
229                             newImageId = path.str;
230                             if (!imageActivationMatch)
231                             {
232                                 imageActivationMatch = std::make_unique<
233                                     sdbusplus::bus::match_t>(
234                                     pldm::utils::DBusHandler::getBus(),
235                                     propertiesChanged(newImageId,
236                                                       "xyz.openbmc_project."
237                                                       "Software.Activation"),
238                                     [this](sdbusplus::message_t& msg) {
239                                         DbusChangedProps props;
240                                         std::string iface;
241                                         msg.read(iface, props);
242                                         const auto itr =
243                                             props.find("Activation");
244                                         if (itr != props.end())
245                                         {
246                                             PropertyValue value = itr->second;
247                                             auto propVal =
248                                                 std::get<std::string>(value);
249                                             if (propVal ==
250                                                 "xyz.openbmc_project.Software."
251                                                 "Activation.Activations.Active")
252                                             {
253                                                 CodeUpdateState state =
254                                                     CodeUpdateState::END;
255                                                 setCodeUpdateProgress(false);
256                                                 auto sensorId =
257                                                     getFirmwareUpdateSensor();
258                                                 sendStateSensorEvent(
259                                                     sensorId,
260                                                     PLDM_STATE_SENSOR_STATE, 0,
261                                                     uint8_t(state),
262                                                     uint8_t(CodeUpdateState::
263                                                                 START));
264                                                 newImageId.clear();
265                                             }
266                                             else if (propVal ==
267                                                          "xyz.openbmc_project."
268                                                          "Software.Activation."
269                                                          "Activations.Failed" ||
270                                                      propVal ==
271                                                          "xyz.openbmc_"
272                                                          "project.Software."
273                                                          "Activation."
274                                                          "Activations."
275                                                          "Invalid")
276                                             {
277                                                 CodeUpdateState state =
278                                                     CodeUpdateState::FAIL;
279                                                 setCodeUpdateProgress(false);
280                                                 auto sensorId =
281                                                     getFirmwareUpdateSensor();
282                                                 sendStateSensorEvent(
283                                                     sensorId,
284                                                     PLDM_STATE_SENSOR_STATE, 0,
285                                                     uint8_t(state),
286                                                     uint8_t(CodeUpdateState::
287                                                                 START));
288                                                 newImageId.clear();
289                                             }
290                                         }
291                                     });
292                             }
293                             auto rc = setRequestedActivation();
294                             if (rc != PLDM_SUCCESS)
295                             {
296                                 CodeUpdateState state = CodeUpdateState::FAIL;
297                                 setCodeUpdateProgress(false);
298                                 auto sensorId = getFirmwareUpdateSensor();
299                                 sendStateSensorEvent(
300                                     sensorId, PLDM_STATE_SENSOR_STATE, 0,
301                                     uint8_t(state),
302                                     uint8_t(CodeUpdateState::START));
303                                 std::cerr
304                                     << "could not set RequestedActivation \n";
305                             }
306                             break;
307                         }
308                     }
309                     catch (const sdbusplus::exception_t& e)
310                     {
311                         std::cerr << "Error in getting Activation status \n";
312                     }
313                 }
314             }
315         }));
316 }
317 
318 void CodeUpdate::processPriorityChangeNotification(
319     const DbusChangedProps& chProperties)
320 {
321     static constexpr auto propName = "Priority";
322     const auto it = chProperties.find(propName);
323     if (it == chProperties.end())
324     {
325         return;
326     }
327     uint8_t newVal = std::get<uint8_t>(it->second);
328     nextBootSide = (newVal == 0) ? currBootSide
329                                  : ((currBootSide == Tside) ? Pside : Tside);
330 }
331 
332 void CodeUpdate::setOemPlatformHandler(
333     pldm::responder::oem_platform::Handler* handler)
334 {
335     oemPlatformHandler = handler;
336 }
337 
338 void CodeUpdate::clearDirPath(const std::string& dirPath)
339 {
340     if (!fs::is_directory(dirPath))
341     {
342         std::cerr << "The directory does not exist, dirPath = " << dirPath
343                   << std::endl;
344         return;
345     }
346     for (const auto& iter : fs::directory_iterator(dirPath))
347     {
348         fs::remove_all(iter);
349     }
350 }
351 
352 void CodeUpdate::sendStateSensorEvent(
353     uint16_t sensorId, enum sensor_event_class_states sensorEventClass,
354     uint8_t sensorOffset, uint8_t eventState, uint8_t prevEventState)
355 {
356     pldm::responder::oem_ibm_platform::Handler* oemIbmPlatformHandler =
357         dynamic_cast<pldm::responder::oem_ibm_platform::Handler*>(
358             oemPlatformHandler);
359     oemIbmPlatformHandler->sendStateSensorEvent(
360         sensorId, sensorEventClass, sensorOffset, eventState, prevEventState);
361 }
362 
363 void CodeUpdate::deleteImage()
364 {
365     static constexpr auto UPDATER_SERVICE =
366         "xyz.openbmc_project.Software.BMC.Updater";
367     static constexpr auto SW_OBJ_PATH = "/xyz/openbmc_project/software";
368     static constexpr auto DELETE_INTF =
369         "xyz.openbmc_project.Collection.DeleteAll";
370 
371     auto& bus = dBusIntf->getBus();
372     try
373     {
374         auto method = bus.new_method_call(UPDATER_SERVICE, SW_OBJ_PATH,
375                                           DELETE_INTF, "DeleteAll");
376         bus.call_noreply(method);
377     }
378     catch (const std::exception& e)
379     {
380         std::cerr << "Failed to delete image, ERROR=" << e.what() << "\n";
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(T const&... 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         std::cerr << "ifstream open error: " << filePath << "\n";
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         std::cerr << "Invalid magic number: " << filePath << "\n";
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                 std::cerr << "Error occurred during the mksqusquashfs call"
548                           << std::endl;
549                 setCodeUpdateProgress(false);
550                 auto sensorId = getFirmwareUpdateSensor();
551                 sendStateSensorEvent(sensorId, PLDM_STATE_SENSOR_STATE, 0,
552                                      uint8_t(CodeUpdateState::FAIL),
553                                      uint8_t(CodeUpdateState::START));
554                 exit(EXIT_FAILURE);
555             }
556 
557             fs::create_directories(updateDirPath);
558 
559             // Extract the BMC tarball content
560             rc = executeCmd("/bin/tar", "-xf", tarImagePath.c_str(), "-C",
561                             updateDirPath);
562             if (rc < 0)
563             {
564                 setCodeUpdateProgress(false);
565                 auto sensorId = getFirmwareUpdateSensor();
566                 sendStateSensorEvent(sensorId, PLDM_STATE_SENSOR_STATE, 0,
567                                      uint8_t(CodeUpdateState::FAIL),
568                                      uint8_t(CodeUpdateState::START));
569                 exit(EXIT_FAILURE);
570             }
571 
572             // Add the hostfw image to the directory where the contents were
573             // extracted
574             fs::copy_file(hostfwImagePath,
575                           fs::path(updateDirPath) / hostfwImageName,
576                           fs::copy_options::overwrite_existing);
577 
578             // Remove the tarball file, then re-generate it with so that the
579             // hostfw image becomes part of the tarball
580             fs::remove(tarImagePath);
581             rc = executeCmd("/bin/tar", "-cf", tarImagePath, ".", "-C",
582                             updateDirPath);
583             if (rc < 0)
584             {
585                 std::cerr
586                     << "Error occurred during the generation of the tarball"
587                     << std::endl;
588                 setCodeUpdateProgress(false);
589                 auto sensorId = getFirmwareUpdateSensor();
590                 sendStateSensorEvent(sensorId, PLDM_STATE_SENSOR_STATE, 0,
591                                      uint8_t(CodeUpdateState::FAIL),
592                                      uint8_t(CodeUpdateState::START));
593                 exit(EXIT_FAILURE);
594             }
595 
596             // Copy the tarball to the update directory to trigger the phosphor
597             // software manager to create a version interface
598             fs::copy_file(tarImagePath, updateImagePath,
599                           fs::copy_options::overwrite_existing);
600 
601             // Cleanup
602             fs::remove_all(updateDirPath);
603             fs::remove_all(lidDirPath);
604             fs::remove_all(imageDirPath);
605 
606             exit(EXIT_SUCCESS);
607         }
608         else if (nextPid < 0)
609         {
610             std::cerr << "Error occurred during fork. ERROR=" << errno
611                       << std::endl;
612             exit(EXIT_FAILURE);
613         }
614 
615         // Do nothing as parent. When parent exits, child will be reparented
616         // under init and be reaped properly.
617         exit(0);
618     }
619     else if (pid > 0)
620     {
621         int status;
622         if (waitpid(pid, &status, 0) < 0)
623         {
624             std::cerr << "Error occurred during waitpid. ERROR=" << errno
625                       << std::endl;
626             return PLDM_ERROR;
627         }
628         else if (WEXITSTATUS(status) != 0)
629         {
630             std::cerr
631                 << "Failed to execute the assembling of the image. STATUS="
632                 << status << std::endl;
633             return PLDM_ERROR;
634         }
635     }
636     else
637     {
638         std::cerr << "Error occurred during fork. ERROR=" << errno << std::endl;
639         return PLDM_ERROR;
640     }
641 
642     return PLDM_SUCCESS;
643 }
644 
645 } // namespace responder
646 } // namespace pldm
647