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