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