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