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(
168             method,
169             std::chrono::duration_cast<microsec>(sec(DBUS_TIMEOUT)).count());
170         reply.read(paths);
171 
172         runningVersion = std::get<std::vector<std::string>>(paths)[0];
173 
174         auto method1 = bus.new_method_call(mapperService, activeObjPath,
175                                            propIntf, "Get");
176         method1.append("xyz.openbmc_project.Association", "endpoints");
177 
178         auto reply1 = bus.call(
179             method1,
180             std::chrono::duration_cast<microsec>(sec(DBUS_TIMEOUT)).count());
181         reply1.read(paths);
182         for (const auto& path : std::get<std::vector<std::string>>(paths))
183         {
184             if (path != runningVersion)
185             {
186                 nonRunningVersion = path;
187                 break;
188             }
189         }
190     }
191     catch (const std::exception& e)
192     {
193         error(
194             "failed to make a d-bus call to Object Mapper Association, ERROR={ERR_EXCEP}",
195             "ERR_EXCEP", e.what());
196         return;
197     }
198 
199     using namespace sdbusplus::bus::match::rules;
200     captureNextBootSideChange.push_back(
201         std::make_unique<sdbusplus::bus::match_t>(
202             pldm::utils::DBusHandler::getBus(),
203             propertiesChanged(runningVersion, redundancyIntf),
204             [this](sdbusplus::message_t& msg) {
205         DbusChangedProps props;
206         std::string iface;
207         msg.read(iface, props);
208         processPriorityChangeNotification(props);
209             }));
210     fwUpdateMatcher.push_back(std::make_unique<sdbusplus::bus::match_t>(
211         pldm::utils::DBusHandler::getBus(),
212         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
213         "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
214         [this](sdbusplus::message_t& msg) {
215         DBusInterfaceAdded interfaces;
216         sdbusplus::message::object_path path;
217         msg.read(path, interfaces);
218 
219         for (auto& interface : interfaces)
220         {
221             if (interface.first == "xyz.openbmc_project.Software.Activation")
222             {
223                 auto imageInterface = "xyz.openbmc_project.Software.Activation";
224                 auto imageObjPath = path.str.c_str();
225 
226                 try
227                 {
228                     auto propVal = dBusIntf->getDbusPropertyVariant(
229                         imageObjPath, "Activation", imageInterface);
230                     const auto& imageProp = std::get<std::string>(propVal);
231                     if (imageProp == "xyz.openbmc_project.Software."
232                                      "Activation.Activations.Ready" &&
233                         isCodeUpdateInProgress())
234                     {
235                         newImageId = path.str;
236                         if (!imageActivationMatch)
237                         {
238                             imageActivationMatch =
239                                 std::make_unique<sdbusplus::bus::match_t>(
240                                     pldm::utils::DBusHandler::getBus(),
241                                     propertiesChanged(newImageId,
242                                                       "xyz.openbmc_project."
243                                                       "Software.Activation"),
244                                     [this](sdbusplus::message_t& msg) {
245                                 DbusChangedProps props;
246                                 std::string iface;
247                                 msg.read(iface, props);
248                                 const auto itr = props.find("Activation");
249                                 if (itr != props.end())
250                                 {
251                                     PropertyValue value = itr->second;
252                                     auto propVal = 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, PLDM_STATE_SENSOR_STATE,
264                                             0, uint8_t(state),
265                                             uint8_t(CodeUpdateState::START));
266                                         newImageId.clear();
267                                     }
268                                     else if (propVal == "xyz.openbmc_project."
269                                                         "Software.Activation."
270                                                         "Activations.Failed" ||
271                                              propVal == "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, PLDM_STATE_SENSOR_STATE,
284                                             0, uint8_t(state),
285                                             uint8_t(CodeUpdateState::START));
286                                         newImageId.clear();
287                                     }
288                                 }
289                                     });
290                         }
291                         auto rc = setRequestedActivation();
292                         if (rc != PLDM_SUCCESS)
293                         {
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                             error("could not set RequestedActivation");
302                         }
303                         break;
304                     }
305                 }
306                 catch (const sdbusplus::exception_t& e)
307                 {
308                     error(
309                         "Error in getting Activation status,ERROR= {ERR_EXCEP}, INTERFACE={IMG_INTERFACE}, OBJECT PATH={OBJ_PATH}",
310                         "ERR_EXCEP", e.what(), "IMG_INTERFACE", imageInterface,
311                         "OBJ_PATH", imageObjPath);
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         error("The directory does not exist, dirPath = {DIR_PATH}", "DIR_PATH",
343               dirPath.c_str());
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         error("Failed to delete image, ERROR={ERR_EXCEP}", "ERR_EXCEP",
381               e.what());
382         return;
383     }
384 }
385 
386 uint8_t fetchBootSide(uint16_t entityInstance, CodeUpdate* codeUpdate)
387 {
388     uint8_t sensorOpState = tSideNum;
389     if (entityInstance == 0)
390     {
391         auto currSide = codeUpdate->fetchCurrentBootSide();
392         if (currSide == Pside)
393         {
394             sensorOpState = pSideNum;
395         }
396     }
397     else if (entityInstance == 1)
398     {
399         auto nextSide = codeUpdate->fetchNextBootSide();
400         if (nextSide == Pside)
401         {
402             sensorOpState = pSideNum;
403         }
404     }
405     else
406     {
407         sensorOpState = PLDM_SENSOR_UNKNOWN;
408     }
409 
410     return sensorOpState;
411 }
412 
413 int setBootSide(uint16_t entityInstance, uint8_t currState,
414                 const std::vector<set_effecter_state_field>& stateField,
415                 CodeUpdate* codeUpdate)
416 {
417     int rc = PLDM_SUCCESS;
418     auto side = (stateField[currState].effecter_state == pSideNum) ? "P" : "T";
419 
420     if (entityInstance == 0)
421     {
422         rc = codeUpdate->setCurrentBootSide(side);
423     }
424     else if (entityInstance == 1)
425     {
426         rc = codeUpdate->setNextBootSide(side);
427     }
428     else
429     {
430         rc = PLDM_PLATFORM_INVALID_STATE_VALUE;
431     }
432     return rc;
433 }
434 
435 template <typename... T>
436 int executeCmd(const T&... t)
437 {
438     std::stringstream cmd;
439     ((cmd << t << " "), ...) << std::endl;
440     FILE* pipe = popen(cmd.str().c_str(), "r");
441     if (!pipe)
442     {
443         throw std::runtime_error("popen() failed!");
444     }
445     int rc = pclose(pipe);
446     if (WEXITSTATUS(rc))
447     {
448         std::cerr << "Error executing: ";
449         ((std::cerr << " " << t), ...);
450         std::cerr << "\n";
451         return -1;
452     }
453 
454     return 0;
455 }
456 
457 int processCodeUpdateLid(const std::string& filePath)
458 {
459     struct LidHeader
460     {
461         uint16_t magicNumber;
462         uint16_t headerVersion;
463         uint32_t lidNumber;
464         uint32_t lidDate;
465         uint16_t lidTime;
466         uint16_t lidClass;
467         uint32_t lidCrc;
468         uint32_t lidSize;
469         uint32_t headerSize;
470     };
471     LidHeader header;
472 
473     std::ifstream ifs(filePath, std::ios::in | std::ios::binary);
474     if (!ifs)
475     {
476         error("ifstream open error: {DIR_PATH}", "DIR_PATH", filePath.c_str());
477         return PLDM_ERROR;
478     }
479     ifs.seekg(0);
480     ifs.read(reinterpret_cast<char*>(&header), sizeof(header));
481 
482     // File size should be the value of lid size minus the header size
483     auto fileSize = fs::file_size(filePath);
484     fileSize -= htonl(header.headerSize);
485     if (fileSize < htonl(header.lidSize))
486     {
487         // File is not completely written yet
488         ifs.close();
489         return PLDM_SUCCESS;
490     }
491 
492     constexpr auto magicNumber = 0x0222;
493     if (htons(header.magicNumber) != magicNumber)
494     {
495         error("Invalid magic number: {DIR_PATH}", "DIR_PATH", filePath.c_str());
496         ifs.close();
497         return PLDM_ERROR;
498     }
499 
500     fs::create_directories(imageDirPath);
501     fs::create_directories(lidDirPath);
502 
503     constexpr auto bmcClass = 0x2000;
504     if (htons(header.lidClass) == bmcClass)
505     {
506         // Skip the header and concatenate the BMC LIDs into a tar file
507         std::ofstream ofs(tarImagePath,
508                           std::ios::out | std::ios::binary | std::ios::app);
509         ifs.seekg(htonl(header.headerSize));
510         ofs << ifs.rdbuf();
511         ofs.flush();
512         ofs.close();
513     }
514     else
515     {
516         std::stringstream lidFileName;
517         lidFileName << std::hex << htonl(header.lidNumber) << ".lid";
518         auto lidNoHeaderPath = fs::path(lidDirPath) / lidFileName.str();
519         std::ofstream ofs(lidNoHeaderPath,
520                           std::ios::out | std::ios::binary | std::ios::trunc);
521         ifs.seekg(htonl(header.headerSize));
522         ofs << ifs.rdbuf();
523         ofs.flush();
524         ofs.close();
525     }
526 
527     ifs.close();
528     fs::remove(filePath);
529     return PLDM_SUCCESS;
530 }
531 
532 int CodeUpdate::assembleCodeUpdateImage()
533 {
534     pid_t pid = fork();
535 
536     if (pid == 0)
537     {
538         pid_t nextPid = fork();
539         if (nextPid == 0)
540         {
541             // Create the hostfw squashfs image from the LID files without
542             // header
543             auto rc = executeCmd("/usr/sbin/mksquashfs", lidDirPath.c_str(),
544                                  hostfwImagePath.c_str(), "-all-root",
545                                  "-no-recovery");
546             if (rc < 0)
547             {
548                 error("Error occurred during the mksqusquashfs call");
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                 error("Error occurred during the generation of the tarball");
586                 setCodeUpdateProgress(false);
587                 auto sensorId = getFirmwareUpdateSensor();
588                 sendStateSensorEvent(sensorId, PLDM_STATE_SENSOR_STATE, 0,
589                                      uint8_t(CodeUpdateState::FAIL),
590                                      uint8_t(CodeUpdateState::START));
591                 exit(EXIT_FAILURE);
592             }
593 
594             // Copy the tarball to the update directory to trigger the phosphor
595             // software manager to create a version interface
596             fs::copy_file(tarImagePath, updateImagePath,
597                           fs::copy_options::overwrite_existing);
598 
599             // Cleanup
600             fs::remove_all(updateDirPath);
601             fs::remove_all(lidDirPath);
602             fs::remove_all(imageDirPath);
603 
604             exit(EXIT_SUCCESS);
605         }
606         else if (nextPid < 0)
607         {
608             error("Error occurred during fork. ERROR={ERR}", "ERR", 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={ERR}", "ERR", errno);
622 
623             return PLDM_ERROR;
624         }
625         else if (WEXITSTATUS(status) != 0)
626         {
627             error(
628                 "Failed to execute the assembling of the image. STATUS={IMG_STATUS}",
629                 "IMG_STATUS", status);
630             return PLDM_ERROR;
631         }
632     }
633     else
634     {
635         error("Error occurred during fork. ERROR={ERR}", "ERR", errno);
636         return PLDM_ERROR;
637     }
638 
639     return PLDM_SUCCESS;
640 }
641 
642 } // namespace responder
643 } // namespace pldm
644