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