1 #pragma once
2 #include "async_resp.hpp"
3 #include "dbus_utility.hpp"
4 #include "error_messages.hpp"
5 #include "generated/enums/resource.hpp"
6 #include "http/utility.hpp"
7 #include "utils/dbus_utils.hpp"
8 
9 #include <boost/system/error_code.hpp>
10 #include <boost/url/format.hpp>
11 #include <sdbusplus/asio/property.hpp>
12 #include <sdbusplus/unpack_properties.hpp>
13 
14 #include <algorithm>
15 #include <array>
16 #include <optional>
17 #include <ranges>
18 #include <string>
19 #include <string_view>
20 #include <vector>
21 
22 namespace redfish
23 {
24 namespace sw_util
25 {
26 /* @brief String that indicates a bios software instance */
27 constexpr const char* biosPurpose =
28     "xyz.openbmc_project.Software.Version.VersionPurpose.Host";
29 
30 /* @brief String that indicates a BMC software instance */
31 constexpr const char* bmcPurpose =
32     "xyz.openbmc_project.Software.Version.VersionPurpose.BMC";
33 
34 inline std::optional<sdbusplus::message::object_path>
getFunctionalSoftwarePath(const std::string & swType)35     getFunctionalSoftwarePath(const std::string& swType)
36 {
37     if (swType == bmcPurpose)
38     {
39         if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
40         {
41             return sdbusplus::message::object_path(
42                 "/xyz/openbmc_project/software/bmc/functional");
43         }
44         else
45         {
46             return sdbusplus::message::object_path(
47                 "/xyz/openbmc_project/software/functional");
48         }
49     }
50     else if (swType == biosPurpose)
51     {
52         if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
53         {
54             return sdbusplus::message::object_path(
55                 "/xyz/openbmc_project/software/bios/functional");
56         }
57         else
58         {
59             return sdbusplus::message::object_path(
60                 "/xyz/openbmc_project/software/functional");
61         }
62     }
63     else
64     {
65         BMCWEB_LOG_ERROR("No valid software path");
66         return std::nullopt;
67     }
68 }
69 
afterGetProperties(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & swId,bool runningImage,const std::string & swVersionPurpose,const std::string & activeVersionPropName,bool populateLinkToImages,const boost::system::error_code & ec,const dbus::utility::DBusPropertiesMap & propertiesList)70 inline void afterGetProperties(
71     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
72     const std::string& swId, bool runningImage,
73     const std::string& swVersionPurpose,
74     const std::string& activeVersionPropName, bool populateLinkToImages,
75     const boost::system::error_code& ec,
76     const dbus::utility::DBusPropertiesMap& propertiesList)
77 {
78     if (ec)
79     {
80         BMCWEB_LOG_ERROR("error_code = {}", ec);
81         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
82         // Have seen the code update app delete the
83         // D-Bus object, during code update, between
84         // the call to mapper and here. Just leave
85         // these properties off if resource not
86         // found.
87         if (ec.value() == EBADR)
88         {
89             return;
90         }
91         messages::internalError(asyncResp->res);
92         return;
93     }
94     // example propertiesList
95     // a{sv} 2 "Version" s
96     // "IBM-witherspoon-OP9-v2.0.10-2.22" "Purpose"
97     // s
98     // "xyz.openbmc_project.Software.Version.VersionPurpose.Host"
99     const std::string* version = nullptr;
100     const std::string* swInvPurpose = nullptr;
101 
102     const bool success = sdbusplus::unpackPropertiesNoThrow(
103         dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose",
104         swInvPurpose, "Version", version);
105 
106     if (!success)
107     {
108         messages::internalError(asyncResp->res);
109         return;
110     }
111 
112     if (version == nullptr || version->empty())
113     {
114         messages::internalError(asyncResp->res);
115         return;
116     }
117     if (swInvPurpose == nullptr || *swInvPurpose != swVersionPurpose)
118     {
119         // Not purpose we're looking for
120         return;
121     }
122 
123     BMCWEB_LOG_DEBUG("Image ID: {}", swId);
124     BMCWEB_LOG_DEBUG("Running image: {}", runningImage);
125     BMCWEB_LOG_DEBUG("Image purpose: {}", *swInvPurpose);
126 
127     if (populateLinkToImages)
128     {
129         nlohmann::json& softwareImageMembers =
130             asyncResp->res.jsonValue["Links"]["SoftwareImages"];
131         // Firmware images are at
132         // /redfish/v1/UpdateService/FirmwareInventory/<Id>
133         // e.g. .../FirmwareInventory/82d3ec86
134         nlohmann::json::object_t member;
135         member["@odata.id"] = boost::urls::format(
136             "/redfish/v1/UpdateService/FirmwareInventory/{}", swId);
137         softwareImageMembers.emplace_back(std::move(member));
138         asyncResp->res.jsonValue["Links"]["SoftwareImages@odata.count"] =
139             softwareImageMembers.size();
140 
141         if (runningImage)
142         {
143             nlohmann::json::object_t runningMember;
144             runningMember["@odata.id"] = boost::urls::format(
145                 "/redfish/v1/UpdateService/FirmwareInventory/{}", swId);
146             // Create the link to the running image
147             asyncResp->res.jsonValue["Links"]["ActiveSoftwareImage"] =
148                 std::move(runningMember);
149         }
150     }
151     if (!activeVersionPropName.empty() && runningImage)
152     {
153         asyncResp->res.jsonValue[activeVersionPropName] = *version;
154     }
155 }
156 
afterGetSubtree(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & swVersionPurpose,const std::string & activeVersionPropName,bool populateLinkToImages,const std::vector<std::string> & functionalSwIds,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)157 inline void afterGetSubtree(
158     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
159     const std::string& swVersionPurpose,
160     const std::string& activeVersionPropName, bool populateLinkToImages,
161     const std::vector<std::string>& functionalSwIds,
162     const boost::system::error_code& ec,
163     const dbus::utility::MapperGetSubTreeResponse& subtree)
164 {
165     if (ec)
166     {
167         BMCWEB_LOG_ERROR("error_code = {}", ec);
168         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
169         messages::internalError(asyncResp->res);
170         return;
171     }
172 
173     BMCWEB_LOG_DEBUG("Found {} images", subtree.size());
174 
175     for (const std::pair<
176              std::string,
177              std::vector<std::pair<std::string, std::vector<std::string>>>>&
178              obj : subtree)
179     {
180         sdbusplus::message::object_path path(obj.first);
181         std::string swId = path.filename();
182         if (swId.empty())
183         {
184             messages::internalError(asyncResp->res);
185             BMCWEB_LOG_ERROR("Invalid software ID");
186 
187             return;
188         }
189 
190         bool runningImage = false;
191         // Look at Ids from
192         // /xyz/openbmc_project/software/functional
193         // to determine if this is
194         // a running image
195         if (std::ranges::find(functionalSwIds, swId) != functionalSwIds.end())
196         {
197             runningImage = true;
198         }
199 
200         // Now grab its version
201         // info
202         dbus::utility::getAllProperties(
203             *crow::connections::systemBus, obj.second[0].first, obj.first,
204             "xyz.openbmc_project.Software.Version",
205             [asyncResp, swId, runningImage, swVersionPurpose,
206              activeVersionPropName, populateLinkToImages](
207                 const boost::system::error_code& ec3,
208                 const dbus::utility::DBusPropertiesMap& propertiesList) {
209                 afterGetProperties(asyncResp, swId, runningImage,
210                                    swVersionPurpose, activeVersionPropName,
211                                    populateLinkToImages, ec3, propertiesList);
212             });
213     }
214 }
215 
afterAssociatedEndpoints(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & swVersionPurpose,const std::string & activeVersionPropName,bool populateLinkToImages,const boost::system::error_code & ec,const dbus::utility::MapperEndPoints & functionalSw)216 inline void afterAssociatedEndpoints(
217     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
218     const std::string& swVersionPurpose,
219     const std::string& activeVersionPropName, bool populateLinkToImages,
220     const boost::system::error_code& ec,
221     const dbus::utility::MapperEndPoints& functionalSw)
222 {
223     BMCWEB_LOG_DEBUG("populateSoftwareInformation enter");
224     if (ec)
225     {
226         BMCWEB_LOG_ERROR("error_code = {}", ec);
227         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
228         // No functional software for this swVersionPurpose, so just
229         return;
230     }
231 
232     if (functionalSw.empty())
233     {
234         // Could keep going and try to populate SoftwareImages
235         // but something is seriously wrong, so just fail
236         BMCWEB_LOG_ERROR("Zero functional software in system");
237         messages::internalError(asyncResp->res);
238         return;
239     }
240 
241     std::vector<std::string> functionalSwIds;
242     // example functionalSw:
243     // v as 2 "/xyz/openbmc_project/software/ace821ef"
244     //        "/xyz/openbmc_project/software/230fb078"
245     for (const auto& sw : functionalSw)
246     {
247         sdbusplus::message::object_path path(sw);
248         std::string leaf = path.filename();
249         if (leaf.empty())
250         {
251             continue;
252         }
253 
254         functionalSwIds.push_back(leaf);
255     }
256 
257     constexpr std::array<std::string_view, 1> interfaces = {
258         "xyz.openbmc_project.Software.Version"};
259     dbus::utility::getSubTree(
260         "/xyz/openbmc_project/software", 0, interfaces,
261         [asyncResp, swVersionPurpose, activeVersionPropName,
262          populateLinkToImages, functionalSwIds](
263             const boost::system::error_code& ec2,
264             const dbus::utility::MapperGetSubTreeResponse& subtree) {
265             afterGetSubtree(asyncResp, swVersionPurpose, activeVersionPropName,
266                             populateLinkToImages, functionalSwIds, ec2,
267                             subtree);
268         });
269 }
270 
271 /**
272  * @brief Populate the running software version and image links
273  *
274  * @param[i,o] asyncResp             Async response object
275  * @param[i]   swVersionPurpose  Indicates what target to look for
276  * @param[i]   activeVersionPropName  Index in asyncResp->res.jsonValue to write
277  * the running software version to
278  * @param[i]   populateLinkToImages  Populate asyncResp->res "Links"
279  * "ActiveSoftwareImage" with a link to the running software image and
280  * "SoftwareImages" with a link to the all its software images
281  *
282  * @return void
283  */
populateSoftwareInformation(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & swVersionPurpose,const std::string & activeVersionPropName,const bool populateLinkToImages)284 inline void populateSoftwareInformation(
285     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
286     const std::string& swVersionPurpose,
287     const std::string& activeVersionPropName, const bool populateLinkToImages)
288 {
289     auto swPath = getFunctionalSoftwarePath(swVersionPurpose);
290     if (!swPath)
291     {
292         BMCWEB_LOG_ERROR("Invalid software type");
293         messages::internalError(asyncResp->res);
294         return;
295     }
296     // Used later to determine running (known on Redfish as active) Sw images
297     dbus::utility::getAssociationEndPoints(
298         swPath.value().str,
299         [asyncResp, swVersionPurpose, activeVersionPropName,
300          populateLinkToImages](
301             const boost::system::error_code& ec,
302             const dbus::utility::MapperEndPoints& functionalSw) {
303             afterAssociatedEndpoints(asyncResp, swVersionPurpose,
304                                      activeVersionPropName,
305                                      populateLinkToImages, ec, functionalSw);
306         });
307 }
308 
309 /**
310  * @brief Translate input swState to Redfish state
311  *
312  * This function will return the corresponding Redfish state
313  *
314  * @param[i]   swState  The OpenBMC software state
315  *
316  * @return The corresponding Redfish state
317  */
getRedfishSwState(const std::string & swState)318 inline resource::State getRedfishSwState(const std::string& swState)
319 {
320     if (swState == "xyz.openbmc_project.Software.Activation.Activations.Active")
321     {
322         return resource::State::Enabled;
323     }
324     if (swState == "xyz.openbmc_project.Software.Activation."
325                    "Activations.Activating")
326     {
327         return resource::State::Updating;
328     }
329     if (swState == "xyz.openbmc_project.Software.Activation."
330                    "Activations.StandbySpare")
331     {
332         return resource::State::StandbySpare;
333     }
334     BMCWEB_LOG_DEBUG("Default sw state {} to Disabled", swState);
335     return resource::State::Disabled;
336 }
337 
338 /**
339  * @brief Translate input swState to Redfish health state
340  *
341  * This function will return the corresponding Redfish health state
342  *
343  * @param[i]   swState  The OpenBMC software state
344  *
345  * @return The corresponding Redfish health state
346  */
getRedfishSwHealth(const std::string & swState)347 inline std::string getRedfishSwHealth(const std::string& swState)
348 {
349     if ((swState ==
350          "xyz.openbmc_project.Software.Activation.Activations.Active") ||
351         (swState == "xyz.openbmc_project.Software.Activation.Activations."
352                     "Activating") ||
353         (swState ==
354          "xyz.openbmc_project.Software.Activation.Activations.Ready"))
355     {
356         return "OK";
357     }
358     BMCWEB_LOG_DEBUG("Sw state {} to Warning", swState);
359     return "Warning";
360 }
361 
362 /**
363  * @brief Put status of input swId into json response
364  *
365  * This function will put the appropriate Redfish state of the input
366  * software id to ["Status"]["State"] within the json response
367  *
368  * @param[i,o] asyncResp    Async response object
369  * @param[i]   swId     The software ID to get status for
370  * @param[i]   dbusSvc  The dbus service implementing the software object
371  *
372  * @return void
373  */
getSwStatus(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::shared_ptr<std::string> & swId,const std::string & dbusSvc)374 inline void getSwStatus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
375                         const std::shared_ptr<std::string>& swId,
376                         const std::string& dbusSvc)
377 {
378     BMCWEB_LOG_DEBUG("getSwStatus: swId {} svc {}", *swId, dbusSvc);
379 
380     dbus::utility::getAllProperties(
381         dbusSvc, "/xyz/openbmc_project/software/" + *swId,
382         "xyz.openbmc_project.Software.Activation",
383         [asyncResp,
384          swId](const boost::system::error_code& ec,
385                const dbus::utility::DBusPropertiesMap& propertiesList) {
386             if (ec)
387             {
388                 // not all swtypes are updateable, this is ok
389                 asyncResp->res.jsonValue["Status"]["State"] =
390                     resource::State::Enabled;
391                 return;
392             }
393 
394             const std::string* swInvActivation = nullptr;
395 
396             const bool success = sdbusplus::unpackPropertiesNoThrow(
397                 dbus_utils::UnpackErrorPrinter(), propertiesList, "Activation",
398                 swInvActivation);
399 
400             if (!success)
401             {
402                 messages::internalError(asyncResp->res);
403                 return;
404             }
405 
406             if (swInvActivation == nullptr)
407             {
408                 messages::internalError(asyncResp->res);
409                 return;
410             }
411 
412             BMCWEB_LOG_DEBUG("getSwStatus: Activation {}", *swInvActivation);
413             asyncResp->res.jsonValue["Status"]["State"] =
414                 getRedfishSwState(*swInvActivation);
415             asyncResp->res.jsonValue["Status"]["Health"] =
416                 getRedfishSwHealth(*swInvActivation);
417         });
418 }
419 
handleUpdateableEndpoints(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::shared_ptr<std::string> & swId,const boost::system::error_code & ec,const dbus::utility::MapperEndPoints & objPaths)420 inline void handleUpdateableEndpoints(
421     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
422     const std::shared_ptr<std::string>& swId,
423     const boost::system::error_code& ec,
424     const dbus::utility::MapperEndPoints& objPaths)
425 {
426     if (ec)
427     {
428         BMCWEB_LOG_DEBUG(" error_code = {} error msg =  {}", ec, ec.message());
429         // System can exist with no updateable software,
430         // so don't throw error here.
431         return;
432     }
433     sdbusplus::message::object_path reqSwObjPath(
434         "/xyz/openbmc_project/software");
435     reqSwObjPath = reqSwObjPath / *swId;
436 
437     if (std::ranges::find(objPaths, reqSwObjPath.str) != objPaths.end())
438     {
439         asyncResp->res.jsonValue["Updateable"] = true;
440         return;
441     }
442 }
443 
444 inline void
handleUpdateableObject(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec,const dbus::utility::MapperGetObject & objectInfo)445     handleUpdateableObject(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
446                            const boost::system::error_code& ec,
447                            const dbus::utility::MapperGetObject& objectInfo)
448 {
449     if (ec)
450     {
451         BMCWEB_LOG_DEBUG(" error_code = {} error msg =  {}", ec, ec.message());
452         // System can exist with no updateable software,
453         // so don't throw error here.
454         return;
455     }
456     if (objectInfo.empty())
457     {
458         BMCWEB_LOG_DEBUG("No updateable software found");
459         // System can exist with no updateable software,
460         // so don't throw error here.
461         return;
462     }
463     asyncResp->res.jsonValue["Updateable"] = true;
464 }
465 
466 /**
467  * @brief Updates programmable status of input swId into json response
468  *
469  * This function checks whether software inventory component
470  * can be programmable or not and fill's the "Updatable"
471  * Property.
472  *
473  * @param[i,o] asyncResp  Async response object
474  * @param[i]   swId       The software ID
475  */
476 inline void
getSwUpdatableStatus(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::shared_ptr<std::string> & swId)477     getSwUpdatableStatus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
478                          const std::shared_ptr<std::string>& swId)
479 {
480     if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
481     {
482         sdbusplus::message::object_path swObjectPath(
483             "/xyz/openbmc_project/software");
484         swObjectPath = swObjectPath / *swId;
485         constexpr std::array<std::string_view, 1> interfaces = {
486             "xyz.openbmc_project.Software.Update"};
487         dbus::utility::getDbusObject(
488             swObjectPath.str, interfaces,
489             std::bind_front(handleUpdateableObject, asyncResp));
490     }
491     else
492     {
493         dbus::utility::getAssociationEndPoints(
494             "/xyz/openbmc_project/software/updateable",
495             std::bind_front(handleUpdateableEndpoints, asyncResp, swId));
496     }
497 }
498 
499 } // namespace sw_util
500 } // namespace redfish
501