1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #pragma once
4 #include "bmcweb_config.h"
5
6 #include "async_resp.hpp"
7 #include "boost_formatters.hpp"
8 #include "dbus_singleton.hpp"
9 #include "dbus_utility.hpp"
10 #include "error_messages.hpp"
11 #include "generated/enums/resource.hpp"
12 #include "logging.hpp"
13 #include "utils/dbus_utils.hpp"
14
15 #include <asm-generic/errno.h>
16
17 #include <boost/system/error_code.hpp>
18 #include <boost/url/format.hpp>
19 #include <nlohmann/json.hpp>
20 #include <sdbusplus/message/native_types.hpp>
21 #include <sdbusplus/unpack_properties.hpp>
22
23 #include <algorithm>
24 #include <array>
25 #include <functional>
26 #include <memory>
27 #include <optional>
28 #include <ranges>
29 #include <string>
30 #include <string_view>
31 #include <utility>
32 #include <vector>
33
34 namespace redfish
35 {
36 namespace sw_util
37 {
38 /* @brief String that indicates a bios software instance */
39 constexpr const char* biosPurpose =
40 "xyz.openbmc_project.Software.Version.VersionPurpose.Host";
41
42 /* @brief String that indicates a BMC software instance */
43 constexpr const char* bmcPurpose =
44 "xyz.openbmc_project.Software.Version.VersionPurpose.BMC";
45
getFunctionalSoftwarePath(const std::string & swType)46 inline std::optional<sdbusplus::message::object_path> getFunctionalSoftwarePath(
47 const std::string& swType)
48 {
49 if (swType == bmcPurpose)
50 {
51 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
52 {
53 return sdbusplus::message::object_path(
54 "/xyz/openbmc_project/software/bmc/functional");
55 }
56 else
57 {
58 return sdbusplus::message::object_path(
59 "/xyz/openbmc_project/software/functional");
60 }
61 }
62 else if (swType == biosPurpose)
63 {
64 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
65 {
66 return sdbusplus::message::object_path(
67 "/xyz/openbmc_project/software/bios/functional");
68 }
69 else
70 {
71 return sdbusplus::message::object_path(
72 "/xyz/openbmc_project/software/functional");
73 }
74 }
75 else
76 {
77 BMCWEB_LOG_ERROR("No valid software path");
78 return std::nullopt;
79 }
80 }
81
afterGetProperties(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & swId,bool runningImage,const std::string & swVersionPurpose,const std::string & activeVersionPropName,bool populateLinkToImages,const boost::system::error_code & ec,const dbus::utility::DBusPropertiesMap & propertiesList)82 inline void afterGetProperties(
83 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
84 const std::string& swId, bool runningImage,
85 const std::string& swVersionPurpose,
86 const std::string& activeVersionPropName, bool populateLinkToImages,
87 const boost::system::error_code& ec,
88 const dbus::utility::DBusPropertiesMap& propertiesList)
89 {
90 if (ec)
91 {
92 // Have seen the code update app delete the
93 // D-Bus object, during code update, between
94 // the call to mapper and here. Just leave
95 // these properties off if resource not
96 // found.
97 if (ec.value() != EBADR)
98 {
99 BMCWEB_LOG_ERROR("error_code = {}", ec);
100 messages::internalError(asyncResp->res);
101 }
102 return;
103 }
104 // example propertiesList
105 // a{sv} 2 "Version" s
106 // "IBM-witherspoon-OP9-v2.0.10-2.22" "Purpose"
107 // s
108 // "xyz.openbmc_project.Software.Version.VersionPurpose.Host"
109 const std::string* version = nullptr;
110 const std::string* swInvPurpose = nullptr;
111
112 const bool success = sdbusplus::unpackPropertiesNoThrow(
113 dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose",
114 swInvPurpose, "Version", version);
115
116 if (!success)
117 {
118 messages::internalError(asyncResp->res);
119 return;
120 }
121
122 if (version == nullptr)
123 {
124 messages::internalError(asyncResp->res);
125 return;
126 }
127 if (swInvPurpose == nullptr || *swInvPurpose != swVersionPurpose)
128 {
129 // Not purpose we're looking for
130 return;
131 }
132
133 BMCWEB_LOG_DEBUG("Image ID: {}", swId);
134 BMCWEB_LOG_DEBUG("Running image: {}", runningImage);
135 BMCWEB_LOG_DEBUG("Image purpose: {}", *swInvPurpose);
136
137 if (populateLinkToImages)
138 {
139 nlohmann::json& softwareImageMembers =
140 asyncResp->res.jsonValue["Links"]["SoftwareImages"];
141 // Firmware images are at
142 // /redfish/v1/UpdateService/FirmwareInventory/<Id>
143 // e.g. .../FirmwareInventory/82d3ec86
144 nlohmann::json::object_t member;
145 member["@odata.id"] = boost::urls::format(
146 "/redfish/v1/UpdateService/FirmwareInventory/{}", swId);
147 softwareImageMembers.emplace_back(std::move(member));
148 asyncResp->res.jsonValue["Links"]["SoftwareImages@odata.count"] =
149 softwareImageMembers.size();
150
151 if (runningImage)
152 {
153 nlohmann::json::object_t runningMember;
154 runningMember["@odata.id"] = boost::urls::format(
155 "/redfish/v1/UpdateService/FirmwareInventory/{}", swId);
156 // Create the link to the running image
157 asyncResp->res.jsonValue["Links"]["ActiveSoftwareImage"] =
158 std::move(runningMember);
159 }
160 }
161 if (!activeVersionPropName.empty() && runningImage)
162 {
163 if (version->empty())
164 {
165 BMCWEB_LOG_INFO("Version is empty for swId: {}", swId);
166 return;
167 }
168 asyncResp->res.jsonValue[activeVersionPropName] = *version;
169 }
170 }
171
afterGetSubtree(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & swVersionPurpose,const std::string & activeVersionPropName,bool populateLinkToImages,const std::vector<std::string> & functionalSwIds,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)172 inline void afterGetSubtree(
173 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
174 const std::string& swVersionPurpose,
175 const std::string& activeVersionPropName, bool populateLinkToImages,
176 const std::vector<std::string>& functionalSwIds,
177 const boost::system::error_code& ec,
178 const dbus::utility::MapperGetSubTreeResponse& subtree)
179 {
180 if (ec)
181 {
182 if (ec.value() != EBADR)
183 {
184 BMCWEB_LOG_ERROR("error_code = {}", ec);
185 messages::internalError(asyncResp->res);
186 }
187 return;
188 }
189
190 BMCWEB_LOG_DEBUG("Found {} images", subtree.size());
191
192 for (const std::pair<
193 std::string,
194 std::vector<std::pair<std::string, std::vector<std::string>>>>&
195 obj : subtree)
196 {
197 sdbusplus::message::object_path path(obj.first);
198 std::string swId = path.filename();
199 if (swId.empty())
200 {
201 messages::internalError(asyncResp->res);
202 BMCWEB_LOG_ERROR("Invalid software ID");
203
204 return;
205 }
206
207 bool runningImage = false;
208 // Look at Ids from
209 // /xyz/openbmc_project/software/functional
210 // to determine if this is
211 // a running image
212 if (std::ranges::find(functionalSwIds, swId) != functionalSwIds.end())
213 {
214 runningImage = true;
215 }
216
217 // Now grab its version
218 // info
219 dbus::utility::getAllProperties(
220 *crow::connections::systemBus, obj.second[0].first, obj.first,
221 "xyz.openbmc_project.Software.Version",
222 [asyncResp, swId, runningImage, swVersionPurpose,
223 activeVersionPropName, populateLinkToImages](
224 const boost::system::error_code& ec3,
225 const dbus::utility::DBusPropertiesMap& propertiesList) {
226 afterGetProperties(asyncResp, swId, runningImage,
227 swVersionPurpose, activeVersionPropName,
228 populateLinkToImages, ec3, propertiesList);
229 });
230 }
231 }
232
afterAssociatedEndpoints(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & swVersionPurpose,const std::string & activeVersionPropName,bool populateLinkToImages,const boost::system::error_code & ec,const dbus::utility::MapperEndPoints & functionalSw)233 inline void afterAssociatedEndpoints(
234 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
235 const std::string& swVersionPurpose,
236 const std::string& activeVersionPropName, bool populateLinkToImages,
237 const boost::system::error_code& ec,
238 const dbus::utility::MapperEndPoints& functionalSw)
239 {
240 BMCWEB_LOG_DEBUG("populateSoftwareInformation enter");
241 if (ec)
242 {
243 BMCWEB_LOG_DEBUG("error_code = {}", ec);
244 // No functional software for this swVersionPurpose, so just
245 return;
246 }
247
248 if (functionalSw.empty())
249 {
250 // Could keep going and try to populate SoftwareImages
251 // but something is seriously wrong, so just fail
252 BMCWEB_LOG_ERROR("Zero functional software in system");
253 messages::internalError(asyncResp->res);
254 return;
255 }
256
257 std::vector<std::string> functionalSwIds;
258 // example functionalSw:
259 // v as 2 "/xyz/openbmc_project/software/ace821ef"
260 // "/xyz/openbmc_project/software/230fb078"
261 for (const auto& sw : functionalSw)
262 {
263 sdbusplus::message::object_path path(sw);
264 std::string leaf = path.filename();
265 if (leaf.empty())
266 {
267 continue;
268 }
269
270 functionalSwIds.push_back(leaf);
271 }
272
273 constexpr std::array<std::string_view, 1> interfaces = {
274 "xyz.openbmc_project.Software.Version"};
275 dbus::utility::getSubTree(
276 "/xyz/openbmc_project/software", 0, interfaces,
277 [asyncResp, swVersionPurpose, activeVersionPropName,
278 populateLinkToImages, functionalSwIds](
279 const boost::system::error_code& ec2,
280 const dbus::utility::MapperGetSubTreeResponse& subtree) {
281 afterGetSubtree(asyncResp, swVersionPurpose, activeVersionPropName,
282 populateLinkToImages, functionalSwIds, ec2,
283 subtree);
284 });
285 }
286
287 /**
288 * @brief Populate the running software version and image links
289 *
290 * @param[i,o] asyncResp Async response object
291 * @param[i] swVersionPurpose Indicates what target to look for
292 * @param[i] activeVersionPropName Index in asyncResp->res.jsonValue to write
293 * the running software version to
294 * @param[i] populateLinkToImages Populate asyncResp->res "Links"
295 * "ActiveSoftwareImage" with a link to the running software image and
296 * "SoftwareImages" with a link to the all its software images
297 *
298 * @return void
299 */
populateSoftwareInformation(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & swVersionPurpose,const std::string & activeVersionPropName,const bool populateLinkToImages)300 inline void populateSoftwareInformation(
301 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
302 const std::string& swVersionPurpose,
303 const std::string& activeVersionPropName, const bool populateLinkToImages)
304 {
305 auto swPath = getFunctionalSoftwarePath(swVersionPurpose);
306 if (!swPath)
307 {
308 BMCWEB_LOG_ERROR("Invalid software type");
309 messages::internalError(asyncResp->res);
310 return;
311 }
312 // Used later to determine running (known on Redfish as active) Sw images
313 dbus::utility::getAssociationEndPoints(
314 swPath.value().str,
315 [asyncResp, swVersionPurpose, activeVersionPropName,
316 populateLinkToImages](
317 const boost::system::error_code& ec,
318 const dbus::utility::MapperEndPoints& functionalSw) {
319 afterAssociatedEndpoints(asyncResp, swVersionPurpose,
320 activeVersionPropName,
321 populateLinkToImages, ec, functionalSw);
322 });
323 }
324
325 /**
326 * @brief Translate input swState to Redfish state
327 *
328 * This function will return the corresponding Redfish state
329 *
330 * @param[i] swState The OpenBMC software state
331 *
332 * @return The corresponding Redfish state
333 */
getRedfishSwState(const std::string & swState)334 inline resource::State getRedfishSwState(const std::string& swState)
335 {
336 if (swState == "xyz.openbmc_project.Software.Activation.Activations.Active")
337 {
338 return resource::State::Enabled;
339 }
340 if (swState == "xyz.openbmc_project.Software.Activation."
341 "Activations.Activating")
342 {
343 return resource::State::Updating;
344 }
345 if (swState == "xyz.openbmc_project.Software.Activation."
346 "Activations.StandbySpare")
347 {
348 return resource::State::StandbySpare;
349 }
350 BMCWEB_LOG_DEBUG("Default sw state {} to Disabled", swState);
351 return resource::State::Disabled;
352 }
353
354 /**
355 * @brief Translate input swState to Redfish health state
356 *
357 * This function will return the corresponding Redfish health state
358 *
359 * @param[i] swState The OpenBMC software state
360 *
361 * @return The corresponding Redfish health state
362 */
getRedfishSwHealth(const std::string & swState)363 inline std::string getRedfishSwHealth(const std::string& swState)
364 {
365 if ((swState ==
366 "xyz.openbmc_project.Software.Activation.Activations.Active") ||
367 (swState == "xyz.openbmc_project.Software.Activation.Activations."
368 "Activating") ||
369 (swState ==
370 "xyz.openbmc_project.Software.Activation.Activations.Ready"))
371 {
372 return "OK";
373 }
374 BMCWEB_LOG_DEBUG("Sw state {} to Warning", swState);
375 return "Warning";
376 }
377
378 /**
379 * @brief Put LowestSupportedVersion of input swId into json response
380 *
381 * This function will put the MinimumVersion from D-Bus of the input
382 * software id to ["LowestSupportedVersion"].
383 *
384 * @param[i,o] asyncResp Async response object
385 * @param[i] swId The software ID to get Minimum Version for
386 * @param[i] dbusSvc The dbus service implementing the software object
387 *
388 * @return void
389 */
getSwMinimumVersion(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::shared_ptr<std::string> & swId,const std::string & dbusSvc)390 inline void getSwMinimumVersion(
391 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
392 const std::shared_ptr<std::string>& swId, const std::string& dbusSvc)
393 {
394 BMCWEB_LOG_DEBUG("getSwMinimumVersion: svc {}, swId {}", dbusSvc, *swId);
395
396 sdbusplus::message::object_path path("/xyz/openbmc_project/software");
397 path /= *swId;
398
399 dbus::utility::getProperty<std::string>(
400 dbusSvc, path, "xyz.openbmc_project.Software.MinimumVersion",
401 "MinimumVersion",
402 [asyncResp](const boost::system::error_code& ec,
403 const std::string& swMinimumVersion) {
404 if (ec)
405 {
406 // not all software has this interface and it is not critical
407 return;
408 }
409
410 BMCWEB_LOG_DEBUG("getSwMinimumVersion: MinimumVersion {}",
411 swMinimumVersion);
412
413 asyncResp->res.jsonValue["LowestSupportedVersion"] =
414 swMinimumVersion;
415 });
416 }
417
418 /**
419 * @brief Put status of input swId into json response
420 *
421 * This function will put the appropriate Redfish state of the input
422 * software id to ["Status"]["State"] within the json response
423 *
424 * @param[i,o] asyncResp Async response object
425 * @param[i] swId The software ID to get status for
426 * @param[i] dbusSvc The dbus service implementing the software object
427 *
428 * @return void
429 */
getSwStatus(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::shared_ptr<std::string> & swId,const std::string & dbusSvc)430 inline void getSwStatus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
431 const std::shared_ptr<std::string>& swId,
432 const std::string& dbusSvc)
433 {
434 BMCWEB_LOG_DEBUG("getSwStatus: swId {} svc {}", *swId, dbusSvc);
435
436 dbus::utility::getAllProperties(
437 dbusSvc, "/xyz/openbmc_project/software/" + *swId,
438 "xyz.openbmc_project.Software.Activation",
439 [asyncResp,
440 swId](const boost::system::error_code& ec,
441 const dbus::utility::DBusPropertiesMap& propertiesList) {
442 if (ec)
443 {
444 // not all swtypes are updateable, this is ok
445 asyncResp->res.jsonValue["Status"]["State"] =
446 resource::State::Enabled;
447 return;
448 }
449
450 const std::string* swInvActivation = nullptr;
451
452 const bool success = sdbusplus::unpackPropertiesNoThrow(
453 dbus_utils::UnpackErrorPrinter(), propertiesList, "Activation",
454 swInvActivation);
455
456 if (!success)
457 {
458 messages::internalError(asyncResp->res);
459 return;
460 }
461
462 if (swInvActivation == nullptr)
463 {
464 messages::internalError(asyncResp->res);
465 return;
466 }
467
468 BMCWEB_LOG_DEBUG("getSwStatus: Activation {}", *swInvActivation);
469 asyncResp->res.jsonValue["Status"]["State"] =
470 getRedfishSwState(*swInvActivation);
471 asyncResp->res.jsonValue["Status"]["Health"] =
472 getRedfishSwHealth(*swInvActivation);
473 });
474 }
475
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)476 inline void handleUpdateableEndpoints(
477 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
478 const std::shared_ptr<std::string>& swId,
479 const boost::system::error_code& ec,
480 const dbus::utility::MapperEndPoints& objPaths)
481 {
482 if (ec)
483 {
484 BMCWEB_LOG_DEBUG(" error_code = {} error msg = {}", ec, ec.message());
485 // System can exist with no updateable software,
486 // so don't throw error here.
487 return;
488 }
489 sdbusplus::message::object_path reqSwObjPath(
490 "/xyz/openbmc_project/software");
491 reqSwObjPath = reqSwObjPath / *swId;
492
493 if (std::ranges::find(objPaths, reqSwObjPath.str) != objPaths.end())
494 {
495 asyncResp->res.jsonValue["Updateable"] = true;
496 return;
497 }
498 }
499
handleUpdateableObject(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec,const dbus::utility::MapperGetObject & objectInfo)500 inline void handleUpdateableObject(
501 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
502 const boost::system::error_code& ec,
503 const dbus::utility::MapperGetObject& objectInfo)
504 {
505 if (ec)
506 {
507 BMCWEB_LOG_DEBUG(" error_code = {} error msg = {}", ec, ec.message());
508 // System can exist with no updateable software,
509 // so don't throw error here.
510 return;
511 }
512 if (objectInfo.empty())
513 {
514 BMCWEB_LOG_DEBUG("No updateable software found");
515 // System can exist with no updateable software,
516 // so don't throw error here.
517 return;
518 }
519 asyncResp->res.jsonValue["Updateable"] = true;
520 }
521
522 /**
523 * @brief Updates programmable status of input swId into json response
524 *
525 * This function checks whether software inventory component
526 * can be programmable or not and fill's the "Updatable"
527 * Property.
528 *
529 * @param[i,o] asyncResp Async response object
530 * @param[i] swId The software ID
531 */
getSwUpdatableStatus(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::shared_ptr<std::string> & swId)532 inline void getSwUpdatableStatus(
533 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
534 const std::shared_ptr<std::string>& swId)
535 {
536 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
537 {
538 sdbusplus::message::object_path swObjectPath(
539 "/xyz/openbmc_project/software");
540 swObjectPath = swObjectPath / *swId;
541 constexpr std::array<std::string_view, 1> interfaces = {
542 "xyz.openbmc_project.Software.Update"};
543 dbus::utility::getDbusObject(
544 swObjectPath.str, interfaces,
545 std::bind_front(handleUpdateableObject, asyncResp));
546 }
547 else
548 {
549 dbus::utility::getAssociationEndPoints(
550 "/xyz/openbmc_project/software/updateable",
551 std::bind_front(handleUpdateableEndpoints, asyncResp, swId));
552 }
553 }
554
555 } // namespace sw_util
556 } // namespace redfish
557