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