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