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