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