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-host-fw";
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     newImageId.clear();
142     return rc;
143 }
144 
145 void CodeUpdate::setVersions()
146 {
147     static constexpr auto mapperService = "xyz.openbmc_project.ObjectMapper";
148     static constexpr auto functionalObjPath =
149         "/xyz/openbmc_project/software/functional";
150     static constexpr auto activeObjPath =
151         "/xyz/openbmc_project/software/active";
152     static constexpr auto propIntf = "org.freedesktop.DBus.Properties";
153 
154     auto& bus = dBusIntf->getBus();
155     try
156     {
157         auto method = bus.new_method_call(mapperService, functionalObjPath,
158                                           propIntf, "Get");
159         method.append("xyz.openbmc_project.Association", "endpoints");
160         std::variant<std::vector<std::string>> paths;
161 
162         auto reply = bus.call(method);
163         reply.read(paths);
164 
165         runningVersion = std::get<std::vector<std::string>>(paths)[0];
166 
167         auto method1 =
168             bus.new_method_call(mapperService, activeObjPath, propIntf, "Get");
169         method1.append("xyz.openbmc_project.Association", "endpoints");
170 
171         auto reply1 = bus.call(method1);
172         reply1.read(paths);
173         for (const auto& path : std::get<std::vector<std::string>>(paths))
174         {
175             if (path != runningVersion)
176             {
177                 nonRunningVersion = path;
178                 break;
179             }
180         }
181     }
182     catch (const std::exception& e)
183     {
184         std::cerr << "failed to make a d-bus call to Object Mapper "
185                      "Association, ERROR="
186                   << e.what() << "\n";
187         return;
188     }
189 
190     using namespace sdbusplus::bus::match::rules;
191     captureNextBootSideChange.push_back(
192         std::make_unique<sdbusplus::bus::match::match>(
193             pldm::utils::DBusHandler::getBus(),
194             propertiesChanged(runningVersion, redundancyIntf),
195             [this](sdbusplus::message::message& msg) {
196                 DbusChangedProps props;
197                 std::string iface;
198                 msg.read(iface, props);
199                 processPriorityChangeNotification(props);
200             }));
201     fwUpdateMatcher = std::make_unique<sdbusplus::bus::match::match>(
202         pldm::utils::DBusHandler::getBus(),
203         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
204         "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
205         [this](sdbusplus::message::message& msg) {
206             DBusInterfaceAdded interfaces;
207             sdbusplus::message::object_path path;
208             msg.read(path, interfaces);
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                     try
218                     {
219                         auto propVal = dBusIntf->getDbusPropertyVariant(
220                             imageObjPath, "Activation", imageInterface);
221                         const auto& imageProp = std::get<std::string>(propVal);
222                         if (imageProp == "xyz.openbmc_project.Software."
223                                          "Activation.Activations.Ready" &&
224                             isCodeUpdateInProgress())
225                         {
226                             newImageId = path.str;
227                             auto rc = setRequestedActivation();
228                             CodeUpdateState state = CodeUpdateState::END;
229                             if (rc != PLDM_SUCCESS)
230                             {
231                                 state = CodeUpdateState::FAIL;
232                                 std::cerr
233                                     << "could not set RequestedActivation \n";
234                             }
235                             setCodeUpdateProgress(false);
236                             auto sensorId = getFirmwareUpdateSensor();
237                             sendStateSensorEvent(
238                                 sensorId, PLDM_STATE_SENSOR_STATE, 0,
239                                 uint8_t(state),
240                                 uint8_t(CodeUpdateState::START));
241                             break;
242                         }
243                     }
244                     catch (const sdbusplus::exception::SdBusError& e)
245                     {
246                         std::cerr << "Error in getting Activation status \n";
247                     }
248                 }
249             }
250         });
251 }
252 
253 void CodeUpdate::processPriorityChangeNotification(
254     const DbusChangedProps& chProperties)
255 {
256     static constexpr auto propName = "Priority";
257     const auto it = chProperties.find(propName);
258     if (it == chProperties.end())
259     {
260         return;
261     }
262     uint8_t newVal = std::get<uint8_t>(it->second);
263     nextBootSide = (newVal == 0) ? currBootSide
264                                  : ((currBootSide == Tside) ? Pside : Tside);
265 }
266 
267 void CodeUpdate::setOemPlatformHandler(
268     pldm::responder::oem_platform::Handler* handler)
269 {
270     oemPlatformHandler = handler;
271 }
272 
273 void CodeUpdate::clearDirPath(const std::string& dirPath)
274 {
275     for (auto& path : fs::directory_iterator(dirPath.c_str()))
276     {
277         fs::remove_all(path);
278     }
279     return;
280 }
281 
282 void CodeUpdate::sendStateSensorEvent(
283     uint16_t sensorId, enum sensor_event_class_states sensorEventClass,
284     uint8_t sensorOffset, uint8_t eventState, uint8_t prevEventState)
285 {
286     pldm::responder::oem_ibm_platform::Handler* oemIbmPlatformHandler =
287         dynamic_cast<pldm::responder::oem_ibm_platform::Handler*>(
288             oemPlatformHandler);
289     oemIbmPlatformHandler->sendStateSensorEvent(
290         sensorId, sensorEventClass, sensorOffset, eventState, prevEventState);
291 }
292 
293 void CodeUpdate::deleteImage()
294 {
295     static constexpr auto UPDATER_SERVICE =
296         "xyz.openbmc_project.Software.BMC.Updater";
297     static constexpr auto SW_OBJ_PATH = "/xyz/openbmc_project/software";
298     static constexpr auto DELETE_INTF =
299         "xyz.openbmc_project.Collection.DeleteAll";
300 
301     auto& bus = dBusIntf->getBus();
302     try
303     {
304         auto method = bus.new_method_call(UPDATER_SERVICE, SW_OBJ_PATH,
305                                           DELETE_INTF, "DeleteAll");
306         bus.call_noreply(method);
307     }
308     catch (const std::exception& e)
309     {
310         std::cerr << "Failed to delete image, ERROR=" << e.what() << "\n";
311         return;
312     }
313 }
314 
315 uint8_t fetchBootSide(uint16_t entityInstance, CodeUpdate* codeUpdate)
316 {
317     uint8_t sensorOpState = tSideNum;
318     if (entityInstance == 0)
319     {
320         auto currSide = codeUpdate->fetchCurrentBootSide();
321         if (currSide == Pside)
322         {
323             sensorOpState = pSideNum;
324         }
325     }
326     else if (entityInstance == 1)
327     {
328         auto nextSide = codeUpdate->fetchNextBootSide();
329         if (nextSide == Pside)
330         {
331             sensorOpState = pSideNum;
332         }
333     }
334     else
335     {
336         sensorOpState = PLDM_SENSOR_UNKNOWN;
337     }
338 
339     return sensorOpState;
340 }
341 
342 int setBootSide(uint16_t entityInstance, uint8_t currState,
343                 const std::vector<set_effecter_state_field>& stateField,
344                 CodeUpdate* codeUpdate)
345 {
346     int rc = PLDM_SUCCESS;
347     auto side = (stateField[currState].effecter_state == pSideNum) ? "P" : "T";
348 
349     if (entityInstance == 0)
350     {
351         rc = codeUpdate->setCurrentBootSide(side);
352     }
353     else if (entityInstance == 1)
354     {
355         rc = codeUpdate->setNextBootSide(side);
356     }
357     else
358     {
359         rc = PLDM_PLATFORM_INVALID_STATE_VALUE;
360     }
361     return rc;
362 }
363 
364 template <typename... T>
365 int executeCmd(T const&... t)
366 {
367     std::stringstream cmd;
368     ((cmd << t << " "), ...) << std::endl;
369     FILE* pipe = popen(cmd.str().c_str(), "r");
370     if (!pipe)
371     {
372         throw std::runtime_error("popen() failed!");
373     }
374     int rc = pclose(pipe);
375     if (WEXITSTATUS(rc))
376     {
377         std::cerr << "Error executing: ";
378         ((std::cerr << " " << t), ...);
379         std::cerr << "\n";
380         return -1;
381     }
382 
383     return 0;
384 }
385 
386 int processCodeUpdateLid(const std::string& filePath)
387 {
388     struct LidHeader
389     {
390         uint16_t magicNumber;
391         uint16_t headerVersion;
392         uint32_t lidNumber;
393         uint32_t lidDate;
394         uint16_t lidTime;
395         uint16_t lidClass;
396         uint32_t lidCrc;
397         uint32_t lidSize;
398         uint32_t headerSize;
399     };
400     LidHeader header;
401 
402     std::ifstream ifs(filePath, std::ios::in | std::ios::binary);
403     if (!ifs)
404     {
405         std::cerr << "ifstream open error: " << filePath << "\n";
406         return PLDM_ERROR;
407     }
408     ifs.seekg(0);
409     ifs.read(reinterpret_cast<char*>(&header), sizeof(header));
410 
411     // File size should be the value of lid size minus the header size
412     auto fileSize = fs::file_size(filePath);
413     fileSize -= htonl(header.headerSize);
414     if (fileSize < htonl(header.lidSize))
415     {
416         // File is not completely written yet
417         ifs.close();
418         return PLDM_SUCCESS;
419     }
420 
421     constexpr auto magicNumber = 0x0222;
422     if (htons(header.magicNumber) != magicNumber)
423     {
424         std::cerr << "Invalid magic number: " << filePath << "\n";
425         ifs.close();
426         return PLDM_ERROR;
427     }
428 
429     fs::create_directories(imageDirPath);
430     fs::create_directories(lidDirPath);
431 
432     constexpr auto bmcClass = 0x2000;
433     if (htons(header.lidClass) == bmcClass)
434     {
435         // Skip the header and concatenate the BMC LIDs into a tar file
436         std::ofstream ofs(tarImagePath,
437                           std::ios::out | std::ios::binary | std::ios::app);
438         ifs.seekg(htonl(header.headerSize));
439         ofs << ifs.rdbuf();
440         ofs.flush();
441         ofs.close();
442     }
443     else
444     {
445         std::stringstream lidFileName;
446         lidFileName << std::hex << htonl(header.lidNumber) << ".lid";
447         auto lidNoHeaderPath = fs::path(lidDirPath) / lidFileName.str();
448         std::ofstream ofs(lidNoHeaderPath,
449                           std::ios::out | std::ios::binary | std::ios::trunc);
450         ifs.seekg(htonl(header.headerSize));
451         ofs << ifs.rdbuf();
452         ofs.flush();
453         ofs.close();
454     }
455 
456     ifs.close();
457     fs::remove(filePath);
458     return PLDM_SUCCESS;
459 }
460 
461 int assembleCodeUpdateImage()
462 {
463     // Create the hostfw squashfs image from the LID files without header
464     auto rc = executeCmd("/usr/sbin/mksquashfs", lidDirPath.c_str(),
465                          hostfwImagePath.c_str(), "-all-root", "-no-recovery");
466     if (rc < 0)
467     {
468         return PLDM_ERROR;
469     }
470 
471     fs::create_directories(updateDirPath);
472 
473     // Extract the BMC tarball content
474     rc = executeCmd("/bin/tar", "-xf", tarImagePath.c_str(), "-C",
475                     updateDirPath);
476     if (rc < 0)
477     {
478         return PLDM_ERROR;
479     }
480 
481     // Add the hostfw image to the directory where the contents were extracted
482     fs::copy_file(hostfwImagePath, fs::path(updateDirPath) / hostfwImageName,
483                   fs::copy_options::overwrite_existing);
484 
485     // Remove the tarball file, then re-generate it with so that the hostfw
486     // image becomes part of the tarball
487     fs::remove(tarImagePath);
488     rc = executeCmd("/bin/tar", "-cf", tarImagePath, ".", "-C", updateDirPath);
489     if (rc < 0)
490     {
491         return PLDM_ERROR;
492     }
493 
494     // Copy the tarball to the update directory to trigger the phosphor software
495     // manager to create a version interface
496     fs::copy_file(tarImagePath, updateImagePath,
497                   fs::copy_options::overwrite_existing);
498 
499     // Cleanup
500     fs::remove_all(updateDirPath);
501     fs::remove_all(lidDirPath);
502     fs::remove_all(imageDirPath);
503 
504     return PLDM_SUCCESS;
505 }
506 
507 } // namespace responder
508 } // namespace pldm
509