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