1 #include "inband_code_update.hpp"
2 
3 #include "libpldm/entity.h"
4 
5 #include "libpldmresponder/pdr.hpp"
6 #include "oem_ibm_handler.hpp"
7 #include "xyz/openbmc_project/Common/error.hpp"
8 
9 #include <arpa/inet.h>
10 
11 #include <sdbusplus/server.hpp>
12 #include <xyz/openbmc_project/Dump/NewDump/server.hpp>
13 
14 #include <exception>
15 #include <fstream>
16 namespace pldm
17 {
18 namespace responder
19 {
20 using namespace oem_ibm_platform;
21 
22 /** @brief Directory where the lid files without a header are stored */
23 auto lidDirPath = fs::path(LID_STAGING_DIR) / "lid";
24 
25 /** @brief Directory where the image files are stored as they are built */
26 auto imageDirPath = fs::path(LID_STAGING_DIR) / "image";
27 
28 /** @brief Directory where the code update tarball files are stored */
29 auto updateDirPath = fs::path(LID_STAGING_DIR) / "update";
30 
31 /** @brief The file name of the code update tarball */
32 constexpr auto tarImageName = "image.tar";
33 
34 /** @brief The file name of the hostfw image */
35 constexpr auto hostfwImageName = "image-hostfw";
36 
37 /** @brief The path to the code update tarball file */
38 auto tarImagePath = fs::path(imageDirPath) / tarImageName;
39 
40 /** @brief The path to the hostfw image */
41 auto hostfwImagePath = fs::path(imageDirPath) / hostfwImageName;
42 
43 /** @brief The path to the tarball file expected by the phosphor software
44  *         manager */
45 auto updateImagePath = fs::path("/tmp/images") / tarImageName;
46 
47 std::string CodeUpdate::fetchCurrentBootSide()
48 {
49     return currBootSide;
50 }
51 
52 std::string CodeUpdate::fetchNextBootSide()
53 {
54     return nextBootSide;
55 }
56 
57 int CodeUpdate::setCurrentBootSide(const std::string& currSide)
58 {
59     currBootSide = currSide;
60     return PLDM_SUCCESS;
61 }
62 
63 int CodeUpdate::setNextBootSide(const std::string& nextSide)
64 {
65     nextBootSide = nextSide;
66     std::string objPath{};
67     if (nextBootSide == currBootSide)
68     {
69         objPath = runningVersion;
70     }
71     else
72     {
73         objPath = nonRunningVersion;
74     }
75     if (objPath.empty())
76     {
77         std::cerr << "no nonRunningVersion present \n";
78         return PLDM_PLATFORM_INVALID_STATE_VALUE;
79     }
80 
81     pldm::utils::DBusMapping dbusMapping{objPath, redundancyIntf, "Priority",
82                                          "uint8_t"};
83     uint8_t val = 0;
84     pldm::utils::PropertyValue value = static_cast<uint8_t>(val);
85     try
86     {
87         dBusIntf->setDbusProperty(dbusMapping, value);
88     }
89     catch (const std::exception& e)
90     {
91         std::cerr << "failed to set the next boot side to " << objPath.c_str()
92                   << " ERROR=" << e.what() << "\n";
93         return PLDM_ERROR;
94     }
95     return PLDM_SUCCESS;
96 }
97 
98 int CodeUpdate::setRequestedApplyTime()
99 {
100     int rc = PLDM_SUCCESS;
101     pldm::utils::PropertyValue value =
102         "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
103     DBusMapping dbusMapping;
104     dbusMapping.objectPath = "/xyz/openbmc_project/software/apply_time";
105     dbusMapping.interface = "xyz.openbmc_project.Software.ApplyTime";
106     dbusMapping.propertyName = "RequestedApplyTime";
107     dbusMapping.propertyType = "string";
108     try
109     {
110         pldm::utils::DBusHandler().setDbusProperty(dbusMapping, value);
111     }
112     catch (const std::exception& e)
113     {
114         std::cerr << "Failed To set RequestedApplyTime property "
115                   << "ERROR=" << e.what() << std::endl;
116         rc = PLDM_ERROR;
117     }
118     return rc;
119 }
120 
121 int CodeUpdate::setRequestedActivation()
122 {
123     int rc = PLDM_SUCCESS;
124     pldm::utils::PropertyValue value =
125         "xyz.openbmc_project.Software.Activation.RequestedActivations.Active";
126     DBusMapping dbusMapping;
127     dbusMapping.objectPath = newImageId;
128     dbusMapping.interface = "xyz.openbmc_project.Software.Activation";
129     dbusMapping.propertyName = "RequestedActivation";
130     dbusMapping.propertyType = "string";
131     try
132     {
133         pldm::utils::DBusHandler().setDbusProperty(dbusMapping, value);
134     }
135     catch (const std::exception& e)
136     {
137         std::cerr << "Failed To set RequestedActivation property"
138                   << "ERROR=" << e.what() << std::endl;
139         rc = PLDM_ERROR;
140     }
141     return rc;
142 }
143 
144 void CodeUpdate::setVersions()
145 {
146     static constexpr auto mapperService = "xyz.openbmc_project.ObjectMapper";
147     static constexpr auto functionalObjPath =
148         "/xyz/openbmc_project/software/functional";
149     static constexpr auto activeObjPath =
150         "/xyz/openbmc_project/software/active";
151     static constexpr auto propIntf = "org.freedesktop.DBus.Properties";
152 
153     auto& bus = dBusIntf->getBus();
154     try
155     {
156         auto method = bus.new_method_call(mapperService, functionalObjPath,
157                                           propIntf, "Get");
158         method.append("xyz.openbmc_project.Association", "endpoints");
159         std::variant<std::vector<std::string>> paths;
160 
161         auto reply = bus.call(method);
162         reply.read(paths);
163 
164         runningVersion = std::get<std::vector<std::string>>(paths)[0];
165 
166         auto method1 =
167             bus.new_method_call(mapperService, activeObjPath, propIntf, "Get");
168         method1.append("xyz.openbmc_project.Association", "endpoints");
169 
170         auto reply1 = bus.call(method1);
171         reply1.read(paths);
172         for (const auto& path : std::get<std::vector<std::string>>(paths))
173         {
174             if (path != runningVersion)
175             {
176                 nonRunningVersion = path;
177                 break;
178             }
179         }
180     }
181     catch (const std::exception& e)
182     {
183         std::cerr << "failed to make a d-bus call to Object Mapper "
184                      "Association, ERROR="
185                   << e.what() << "\n";
186         return;
187     }
188 
189     using namespace sdbusplus::bus::match::rules;
190     captureNextBootSideChange.push_back(
191         std::make_unique<sdbusplus::bus::match::match>(
192             pldm::utils::DBusHandler::getBus(),
193             propertiesChanged(runningVersion, redundancyIntf),
194             [this](sdbusplus::message::message& msg) {
195                 DbusChangedProps props;
196                 std::string iface;
197                 msg.read(iface, props);
198                 processPriorityChangeNotification(props);
199             }));
200     fwUpdateMatcher.push_back(std::make_unique<sdbusplus::bus::match::match>(
201         pldm::utils::DBusHandler::getBus(),
202         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
203         "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
204         [this](sdbusplus::message::message& msg) {
205             DBusInterfaceAdded interfaces;
206             sdbusplus::message::object_path path;
207             msg.read(path, interfaces);
208 
209             for (auto& interface : interfaces)
210             {
211                 if (interface.first ==
212                     "xyz.openbmc_project.Software.Activation")
213                 {
214                     auto imageInterface =
215                         "xyz.openbmc_project.Software.Activation";
216                     auto imageObjPath = path.str.c_str();
217 
218                     try
219                     {
220                         auto propVal = dBusIntf->getDbusPropertyVariant(
221                             imageObjPath, "Activation", imageInterface);
222                         const auto& imageProp = std::get<std::string>(propVal);
223                         if (imageProp == "xyz.openbmc_project.Software."
224                                          "Activation.Activations.Ready" &&
225                             isCodeUpdateInProgress())
226                         {
227                             newImageId = path.str;
228                             if (!imageActivationMatch)
229                             {
230                                 imageActivationMatch = std::make_unique<
231                                     sdbusplus::bus::match::match>(
232                                     pldm::utils::DBusHandler::getBus(),
233                                     propertiesChanged(newImageId,
234                                                       "xyz.openbmc_project."
235                                                       "Software.Activation"),
236                                     [this](sdbusplus::message::message& msg) {
237                                         DbusChangedProps props;
238                                         std::string iface;
239                                         msg.read(iface, props);
240                                         const auto itr =
241                                             props.find("Activation");
242                                         if (itr != props.end())
243                                         {
244                                             PropertyValue value = itr->second;
245                                             auto propVal =
246                                                 std::get<std::string>(value);
247                                             if (propVal ==
248                                                 "xyz.openbmc_project.Software."
249                                                 "Activation.Activations.Active")
250                                             {
251                                                 CodeUpdateState state =
252                                                     CodeUpdateState::END;
253                                                 setCodeUpdateProgress(false);
254                                                 auto sensorId =
255                                                     getFirmwareUpdateSensor();
256                                                 sendStateSensorEvent(
257                                                     sensorId,
258                                                     PLDM_STATE_SENSOR_STATE, 0,
259                                                     uint8_t(state),
260                                                     uint8_t(CodeUpdateState::
261                                                                 START));
262                                                 newImageId.clear();
263                                             }
264                                             else if (propVal ==
265                                                          "xyz.openbmc_project."
266                                                          "Software.Activation."
267                                                          "Activations.Failed" ||
268                                                      propVal ==
269                                                          "xyz.openbmc_"
270                                                          "project.Software."
271                                                          "Activation."
272                                                          "Activations."
273                                                          "Invalid")
274                                             {
275                                                 CodeUpdateState state =
276                                                     CodeUpdateState::FAIL;
277                                                 setCodeUpdateProgress(false);
278                                                 auto sensorId =
279                                                     getFirmwareUpdateSensor();
280                                                 sendStateSensorEvent(
281                                                     sensorId,
282                                                     PLDM_STATE_SENSOR_STATE, 0,
283                                                     uint8_t(state),
284                                                     uint8_t(CodeUpdateState::
285                                                                 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                                 std::cerr
302                                     << "could not set RequestedActivation \n";
303                             }
304                             break;
305                         }
306                     }
307                     catch (const sdbusplus::exception::SdBusError& e)
308                     {
309                         std::cerr << "Error in getting Activation status \n";
310                     }
311                 }
312             }
313         }));
314 }
315 
316 void CodeUpdate::processPriorityChangeNotification(
317     const DbusChangedProps& chProperties)
318 {
319     static constexpr auto propName = "Priority";
320     const auto it = chProperties.find(propName);
321     if (it == chProperties.end())
322     {
323         return;
324     }
325     uint8_t newVal = std::get<uint8_t>(it->second);
326     nextBootSide = (newVal == 0) ? currBootSide
327                                  : ((currBootSide == Tside) ? Pside : Tside);
328 }
329 
330 void CodeUpdate::setOemPlatformHandler(
331     pldm::responder::oem_platform::Handler* handler)
332 {
333     oemPlatformHandler = handler;
334 }
335 
336 void CodeUpdate::clearDirPath(const std::string& dirPath)
337 {
338     for (auto& path : fs::directory_iterator(dirPath.c_str()))
339     {
340         fs::remove_all(path);
341     }
342     return;
343 }
344 
345 void CodeUpdate::sendStateSensorEvent(
346     uint16_t sensorId, enum sensor_event_class_states sensorEventClass,
347     uint8_t sensorOffset, uint8_t eventState, uint8_t prevEventState)
348 {
349     pldm::responder::oem_ibm_platform::Handler* oemIbmPlatformHandler =
350         dynamic_cast<pldm::responder::oem_ibm_platform::Handler*>(
351             oemPlatformHandler);
352     oemIbmPlatformHandler->sendStateSensorEvent(
353         sensorId, sensorEventClass, sensorOffset, eventState, prevEventState);
354 }
355 
356 void CodeUpdate::deleteImage()
357 {
358     static constexpr auto UPDATER_SERVICE =
359         "xyz.openbmc_project.Software.BMC.Updater";
360     static constexpr auto SW_OBJ_PATH = "/xyz/openbmc_project/software";
361     static constexpr auto DELETE_INTF =
362         "xyz.openbmc_project.Collection.DeleteAll";
363 
364     auto& bus = dBusIntf->getBus();
365     try
366     {
367         auto method = bus.new_method_call(UPDATER_SERVICE, SW_OBJ_PATH,
368                                           DELETE_INTF, "DeleteAll");
369         bus.call_noreply(method);
370     }
371     catch (const std::exception& e)
372     {
373         std::cerr << "Failed to delete image, ERROR=" << e.what() << "\n";
374         return;
375     }
376 }
377 
378 uint8_t fetchBootSide(uint16_t entityInstance, CodeUpdate* codeUpdate)
379 {
380     uint8_t sensorOpState = tSideNum;
381     if (entityInstance == 0)
382     {
383         auto currSide = codeUpdate->fetchCurrentBootSide();
384         if (currSide == Pside)
385         {
386             sensorOpState = pSideNum;
387         }
388     }
389     else if (entityInstance == 1)
390     {
391         auto nextSide = codeUpdate->fetchNextBootSide();
392         if (nextSide == Pside)
393         {
394             sensorOpState = pSideNum;
395         }
396     }
397     else
398     {
399         sensorOpState = PLDM_SENSOR_UNKNOWN;
400     }
401 
402     return sensorOpState;
403 }
404 
405 int setBootSide(uint16_t entityInstance, uint8_t currState,
406                 const std::vector<set_effecter_state_field>& stateField,
407                 CodeUpdate* codeUpdate)
408 {
409     int rc = PLDM_SUCCESS;
410     auto side = (stateField[currState].effecter_state == pSideNum) ? "P" : "T";
411 
412     if (entityInstance == 0)
413     {
414         rc = codeUpdate->setCurrentBootSide(side);
415     }
416     else if (entityInstance == 1)
417     {
418         rc = codeUpdate->setNextBootSide(side);
419     }
420     else
421     {
422         rc = PLDM_PLATFORM_INVALID_STATE_VALUE;
423     }
424     return rc;
425 }
426 
427 template <typename... T>
428 int executeCmd(T const&... t)
429 {
430     std::stringstream cmd;
431     ((cmd << t << " "), ...) << std::endl;
432     FILE* pipe = popen(cmd.str().c_str(), "r");
433     if (!pipe)
434     {
435         throw std::runtime_error("popen() failed!");
436     }
437     int rc = pclose(pipe);
438     if (WEXITSTATUS(rc))
439     {
440         std::cerr << "Error executing: ";
441         ((std::cerr << " " << t), ...);
442         std::cerr << "\n";
443         return -1;
444     }
445 
446     return 0;
447 }
448 
449 int processCodeUpdateLid(const std::string& filePath)
450 {
451     struct LidHeader
452     {
453         uint16_t magicNumber;
454         uint16_t headerVersion;
455         uint32_t lidNumber;
456         uint32_t lidDate;
457         uint16_t lidTime;
458         uint16_t lidClass;
459         uint32_t lidCrc;
460         uint32_t lidSize;
461         uint32_t headerSize;
462     };
463     LidHeader header;
464 
465     std::ifstream ifs(filePath, std::ios::in | std::ios::binary);
466     if (!ifs)
467     {
468         std::cerr << "ifstream open error: " << filePath << "\n";
469         return PLDM_ERROR;
470     }
471     ifs.seekg(0);
472     ifs.read(reinterpret_cast<char*>(&header), sizeof(header));
473 
474     // File size should be the value of lid size minus the header size
475     auto fileSize = fs::file_size(filePath);
476     fileSize -= htonl(header.headerSize);
477     if (fileSize < htonl(header.lidSize))
478     {
479         // File is not completely written yet
480         ifs.close();
481         return PLDM_SUCCESS;
482     }
483 
484     constexpr auto magicNumber = 0x0222;
485     if (htons(header.magicNumber) != magicNumber)
486     {
487         std::cerr << "Invalid magic number: " << filePath << "\n";
488         ifs.close();
489         return PLDM_ERROR;
490     }
491 
492     fs::create_directories(imageDirPath);
493     fs::create_directories(lidDirPath);
494 
495     constexpr auto bmcClass = 0x2000;
496     if (htons(header.lidClass) == bmcClass)
497     {
498         // Skip the header and concatenate the BMC LIDs into a tar file
499         std::ofstream ofs(tarImagePath,
500                           std::ios::out | std::ios::binary | std::ios::app);
501         ifs.seekg(htonl(header.headerSize));
502         ofs << ifs.rdbuf();
503         ofs.flush();
504         ofs.close();
505     }
506     else
507     {
508         std::stringstream lidFileName;
509         lidFileName << std::hex << htonl(header.lidNumber) << ".lid";
510         auto lidNoHeaderPath = fs::path(lidDirPath) / lidFileName.str();
511         std::ofstream ofs(lidNoHeaderPath,
512                           std::ios::out | std::ios::binary | std::ios::trunc);
513         ifs.seekg(htonl(header.headerSize));
514         ofs << ifs.rdbuf();
515         ofs.flush();
516         ofs.close();
517     }
518 
519     ifs.close();
520     fs::remove(filePath);
521     return PLDM_SUCCESS;
522 }
523 
524 int assembleCodeUpdateImage()
525 {
526     // Create the hostfw squashfs image from the LID files without header
527     auto rc = executeCmd("/usr/sbin/mksquashfs", lidDirPath.c_str(),
528                          hostfwImagePath.c_str(), "-all-root", "-no-recovery");
529     if (rc < 0)
530     {
531         return PLDM_ERROR;
532     }
533 
534     fs::create_directories(updateDirPath);
535 
536     // Extract the BMC tarball content
537     rc = executeCmd("/bin/tar", "-xf", tarImagePath.c_str(), "-C",
538                     updateDirPath);
539     if (rc < 0)
540     {
541         return PLDM_ERROR;
542     }
543 
544     // Add the hostfw image to the directory where the contents were extracted
545     fs::copy_file(hostfwImagePath, fs::path(updateDirPath) / hostfwImageName,
546                   fs::copy_options::overwrite_existing);
547 
548     // Remove the tarball file, then re-generate it with so that the hostfw
549     // image becomes part of the tarball
550     fs::remove(tarImagePath);
551     rc = executeCmd("/bin/tar", "-cf", tarImagePath, ".", "-C", updateDirPath);
552     if (rc < 0)
553     {
554         return PLDM_ERROR;
555     }
556 
557     // Copy the tarball to the update directory to trigger the phosphor software
558     // manager to create a version interface
559     fs::copy_file(tarImagePath, updateImagePath,
560                   fs::copy_options::overwrite_existing);
561 
562     // Cleanup
563     fs::remove_all(updateDirPath);
564     fs::remove_all(lidDirPath);
565     fs::remove_all(imageDirPath);
566 
567     return PLDM_SUCCESS;
568 }
569 
570 } // namespace responder
571 } // namespace pldm
572