xref: /openbmc/bmcweb/redfish-core/lib/update_service.hpp (revision 40e9b92ec19acffb46f83a6e55b18974da5d708e)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
4 #pragma once
5 
6 #include "bmcweb_config.h"
7 
8 #include "app.hpp"
9 #include "dbus_utility.hpp"
10 #include "error_messages.hpp"
11 #include "generated/enums/update_service.hpp"
12 #include "multipart_parser.hpp"
13 #include "ossl_random.hpp"
14 #include "query.hpp"
15 #include "registries/privilege_registry.hpp"
16 #include "task.hpp"
17 #include "task_messages.hpp"
18 #include "utils/collection.hpp"
19 #include "utils/dbus_utils.hpp"
20 #include "utils/json_utils.hpp"
21 #include "utils/sw_utils.hpp"
22 
23 #include <sys/mman.h>
24 
25 #include <boost/system/error_code.hpp>
26 #include <boost/url/format.hpp>
27 #include <sdbusplus/asio/property.hpp>
28 #include <sdbusplus/bus/match.hpp>
29 #include <sdbusplus/unpack_properties.hpp>
30 
31 #include <array>
32 #include <cstddef>
33 #include <filesystem>
34 #include <functional>
35 #include <iterator>
36 #include <memory>
37 #include <optional>
38 #include <string>
39 #include <string_view>
40 #include <unordered_map>
41 #include <vector>
42 
43 namespace redfish
44 {
45 
46 // Match signals added on software path
47 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
48 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateMatcher;
49 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
50 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateErrorMatcher;
51 // Only allow one update at a time
52 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
53 static bool fwUpdateInProgress = false;
54 // Timer for software available
55 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
56 static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer;
57 
58 struct MemoryFileDescriptor
59 {
60     int fd = -1;
61 
MemoryFileDescriptorredfish::MemoryFileDescriptor62     explicit MemoryFileDescriptor(const std::string& filename) :
63         fd(memfd_create(filename.c_str(), 0))
64     {}
65 
66     MemoryFileDescriptor(const MemoryFileDescriptor&) = default;
MemoryFileDescriptorredfish::MemoryFileDescriptor67     MemoryFileDescriptor(MemoryFileDescriptor&& other) noexcept : fd(other.fd)
68     {
69         other.fd = -1;
70     }
71     MemoryFileDescriptor& operator=(const MemoryFileDescriptor&) = delete;
72     MemoryFileDescriptor& operator=(MemoryFileDescriptor&&) = default;
73 
~MemoryFileDescriptorredfish::MemoryFileDescriptor74     ~MemoryFileDescriptor()
75     {
76         if (fd != -1)
77         {
78             close(fd);
79         }
80     }
81 
rewindredfish::MemoryFileDescriptor82     bool rewind() const
83     {
84         if (lseek(fd, 0, SEEK_SET) == -1)
85         {
86             BMCWEB_LOG_ERROR("Failed to seek to beginning of image memfd");
87             return false;
88         }
89         return true;
90     }
91 };
92 
cleanUp()93 inline void cleanUp()
94 {
95     fwUpdateInProgress = false;
96     fwUpdateMatcher = nullptr;
97     fwUpdateErrorMatcher = nullptr;
98 }
99 
activateImage(const std::string & objPath,const std::string & service)100 inline void activateImage(const std::string& objPath,
101                           const std::string& service)
102 {
103     BMCWEB_LOG_DEBUG("Activate image for {} {}", objPath, service);
104     sdbusplus::asio::setProperty(
105         *crow::connections::systemBus, service, objPath,
106         "xyz.openbmc_project.Software.Activation", "RequestedActivation",
107         "xyz.openbmc_project.Software.Activation.RequestedActivations.Active",
108         [](const boost::system::error_code& ec) {
109             if (ec)
110             {
111                 BMCWEB_LOG_DEBUG("error_code = {}", ec);
112                 BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
113             }
114         });
115 }
116 
handleCreateTask(const boost::system::error_code & ec2,sdbusplus::message_t & msg,const std::shared_ptr<task::TaskData> & taskData)117 inline bool handleCreateTask(const boost::system::error_code& ec2,
118                              sdbusplus::message_t& msg,
119                              const std::shared_ptr<task::TaskData>& taskData)
120 {
121     if (ec2)
122     {
123         return task::completed;
124     }
125 
126     std::string iface;
127     dbus::utility::DBusPropertiesMap values;
128 
129     std::string index = std::to_string(taskData->index);
130     msg.read(iface, values);
131 
132     if (iface == "xyz.openbmc_project.Software.Activation")
133     {
134         const std::string* state = nullptr;
135         for (const auto& property : values)
136         {
137             if (property.first == "Activation")
138             {
139                 state = std::get_if<std::string>(&property.second);
140                 if (state == nullptr)
141                 {
142                     taskData->messages.emplace_back(messages::internalError());
143                     return task::completed;
144                 }
145             }
146         }
147 
148         if (state == nullptr)
149         {
150             return !task::completed;
151         }
152 
153         if (state->ends_with("Invalid") || state->ends_with("Failed"))
154         {
155             taskData->state = "Exception";
156             taskData->status = "Warning";
157             taskData->messages.emplace_back(messages::taskAborted(index));
158             return task::completed;
159         }
160 
161         if (state->ends_with("Staged"))
162         {
163             taskData->state = "Stopping";
164             taskData->messages.emplace_back(messages::taskPaused(index));
165 
166             // its staged, set a long timer to
167             // allow them time to complete the
168             // update (probably cycle the
169             // system) if this expires then
170             // task will be canceled
171             taskData->extendTimer(std::chrono::hours(5));
172             return !task::completed;
173         }
174 
175         if (state->ends_with("Active"))
176         {
177             taskData->messages.emplace_back(messages::taskCompletedOK(index));
178             taskData->state = "Completed";
179             return task::completed;
180         }
181     }
182     else if (iface == "xyz.openbmc_project.Software.ActivationProgress")
183     {
184         const uint8_t* progress = nullptr;
185         for (const auto& property : values)
186         {
187             if (property.first == "Progress")
188             {
189                 progress = std::get_if<uint8_t>(&property.second);
190                 if (progress == nullptr)
191                 {
192                     taskData->messages.emplace_back(messages::internalError());
193                     return task::completed;
194                 }
195             }
196         }
197 
198         if (progress == nullptr)
199         {
200             return !task::completed;
201         }
202         taskData->percentComplete = *progress;
203         taskData->messages.emplace_back(
204             messages::taskProgressChanged(index, *progress));
205 
206         // if we're getting status updates it's
207         // still alive, update timer
208         taskData->extendTimer(std::chrono::minutes(5));
209     }
210 
211     // as firmware update often results in a
212     // reboot, the task  may never "complete"
213     // unless it is an error
214 
215     return !task::completed;
216 }
217 
createTask(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload && payload,const sdbusplus::message::object_path & objPath)218 inline void createTask(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
219                        task::Payload&& payload,
220                        const sdbusplus::message::object_path& objPath)
221 {
222     std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
223         std::bind_front(handleCreateTask),
224         "type='signal',interface='org.freedesktop.DBus.Properties',"
225         "member='PropertiesChanged',path='" +
226             objPath.str + "'");
227     task->startTimer(std::chrono::minutes(5));
228     task->populateResp(asyncResp->res);
229     task->payload.emplace(std::move(payload));
230 }
231 
232 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
233 // then no asyncResp updates will occur
234 inline void
softwareInterfaceAdded(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,sdbusplus::message_t & m,task::Payload && payload)235     softwareInterfaceAdded(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
236                            sdbusplus::message_t& m, task::Payload&& payload)
237 {
238     dbus::utility::DBusInterfacesMap interfacesProperties;
239 
240     sdbusplus::message::object_path objPath;
241 
242     m.read(objPath, interfacesProperties);
243 
244     BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
245     for (const auto& interface : interfacesProperties)
246     {
247         BMCWEB_LOG_DEBUG("interface = {}", interface.first);
248 
249         if (interface.first == "xyz.openbmc_project.Software.Activation")
250         {
251             // Retrieve service and activate
252             constexpr std::array<std::string_view, 1> interfaces = {
253                 "xyz.openbmc_project.Software.Activation"};
254             dbus::utility::getDbusObject(
255                 objPath.str, interfaces,
256                 [objPath, asyncResp, payload(std::move(payload))](
257                     const boost::system::error_code& ec,
258                     const std::vector<
259                         std::pair<std::string, std::vector<std::string>>>&
260                         objInfo) mutable {
261                     if (ec)
262                     {
263                         BMCWEB_LOG_DEBUG("error_code = {}", ec);
264                         BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
265                         if (asyncResp)
266                         {
267                             messages::internalError(asyncResp->res);
268                         }
269                         cleanUp();
270                         return;
271                     }
272                     // Ensure we only got one service back
273                     if (objInfo.size() != 1)
274                     {
275                         BMCWEB_LOG_ERROR("Invalid Object Size {}",
276                                          objInfo.size());
277                         if (asyncResp)
278                         {
279                             messages::internalError(asyncResp->res);
280                         }
281                         cleanUp();
282                         return;
283                     }
284                     // cancel timer only when
285                     // xyz.openbmc_project.Software.Activation interface
286                     // is added
287                     fwAvailableTimer = nullptr;
288 
289                     activateImage(objPath.str, objInfo[0].first);
290                     if (asyncResp)
291                     {
292                         createTask(asyncResp, std::move(payload), objPath);
293                     }
294                     fwUpdateInProgress = false;
295                 });
296 
297             break;
298         }
299     }
300 }
301 
afterAvailbleTimerAsyncWait(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec)302 inline void afterAvailbleTimerAsyncWait(
303     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
304     const boost::system::error_code& ec)
305 {
306     cleanUp();
307     if (ec == boost::asio::error::operation_aborted)
308     {
309         // expected, we were canceled before the timer completed.
310         return;
311     }
312     BMCWEB_LOG_ERROR("Timed out waiting for firmware object being created");
313     BMCWEB_LOG_ERROR("FW image may has already been uploaded to server");
314     if (ec)
315     {
316         BMCWEB_LOG_ERROR("Async_wait failed{}", ec);
317         return;
318     }
319     if (asyncResp)
320     {
321         redfish::messages::internalError(asyncResp->res);
322     }
323 }
324 
325 inline void
handleUpdateErrorType(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & url,const std::string & type)326     handleUpdateErrorType(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
327                           const std::string& url, const std::string& type)
328 {
329     // NOLINTBEGIN(bugprone-branch-clone)
330     if (type == "xyz.openbmc_project.Software.Image.Error.UnTarFailure")
331     {
332         messages::missingOrMalformedPart(asyncResp->res);
333     }
334     else if (type ==
335              "xyz.openbmc_project.Software.Image.Error.ManifestFileFailure")
336     {
337         messages::missingOrMalformedPart(asyncResp->res);
338     }
339     else if (type == "xyz.openbmc_project.Software.Image.Error.ImageFailure")
340     {
341         messages::missingOrMalformedPart(asyncResp->res);
342     }
343     else if (type == "xyz.openbmc_project.Software.Version.Error.AlreadyExists")
344     {
345         messages::resourceAlreadyExists(asyncResp->res, "UpdateService",
346                                         "Version", "uploaded version");
347     }
348     else if (type == "xyz.openbmc_project.Software.Image.Error.BusyFailure")
349     {
350         messages::serviceTemporarilyUnavailable(asyncResp->res, url);
351     }
352     else if (type == "xyz.openbmc_project.Software.Version.Error.Incompatible")
353     {
354         messages::internalError(asyncResp->res);
355     }
356     else if (type ==
357              "xyz.openbmc_project.Software.Version.Error.ExpiredAccessKey")
358     {
359         messages::internalError(asyncResp->res);
360     }
361     else if (type ==
362              "xyz.openbmc_project.Software.Version.Error.InvalidSignature")
363     {
364         messages::missingOrMalformedPart(asyncResp->res);
365     }
366     else if (type ==
367                  "xyz.openbmc_project.Software.Image.Error.InternalFailure" ||
368              type == "xyz.openbmc_project.Software.Version.Error.HostFile")
369     {
370         BMCWEB_LOG_ERROR("Software Image Error type={}", type);
371         messages::internalError(asyncResp->res);
372     }
373     else
374     {
375         // Unrelated error types. Ignored
376         BMCWEB_LOG_INFO("Non-Software-related Error type={}. Ignored", type);
377         return;
378     }
379     // NOLINTEND(bugprone-branch-clone)
380     // Clear the timer
381     fwAvailableTimer = nullptr;
382 }
383 
384 inline void
afterUpdateErrorMatcher(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & url,sdbusplus::message_t & m)385     afterUpdateErrorMatcher(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
386                             const std::string& url, sdbusplus::message_t& m)
387 {
388     dbus::utility::DBusInterfacesMap interfacesProperties;
389     sdbusplus::message::object_path objPath;
390     m.read(objPath, interfacesProperties);
391     BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
392     for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>&
393              interface : interfacesProperties)
394     {
395         if (interface.first == "xyz.openbmc_project.Logging.Entry")
396         {
397             for (const std::pair<std::string, dbus::utility::DbusVariantType>&
398                      value : interface.second)
399             {
400                 if (value.first != "Message")
401                 {
402                     continue;
403                 }
404                 const std::string* type =
405                     std::get_if<std::string>(&value.second);
406                 if (type == nullptr)
407                 {
408                     // if this was our message, timeout will cover it
409                     return;
410                 }
411                 handleUpdateErrorType(asyncResp, url, *type);
412             }
413         }
414     }
415 }
416 
417 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
418 // then no asyncResp updates will occur
monitorForSoftwareAvailable(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const crow::Request & req,const std::string & url,int timeoutTimeSeconds=25)419 inline void monitorForSoftwareAvailable(
420     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
421     const crow::Request& req, const std::string& url,
422     int timeoutTimeSeconds = 25)
423 {
424     // Only allow one FW update at a time
425     if (fwUpdateInProgress)
426     {
427         if (asyncResp)
428         {
429             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
430         }
431         return;
432     }
433 
434     if (req.ioService == nullptr)
435     {
436         messages::internalError(asyncResp->res);
437         return;
438     }
439 
440     fwAvailableTimer =
441         std::make_unique<boost::asio::steady_timer>(*req.ioService);
442 
443     fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds));
444 
445     fwAvailableTimer->async_wait(
446         std::bind_front(afterAvailbleTimerAsyncWait, asyncResp));
447 
448     task::Payload payload(req);
449     auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable {
450         BMCWEB_LOG_DEBUG("Match fired");
451         softwareInterfaceAdded(asyncResp, m, std::move(payload));
452     };
453 
454     fwUpdateInProgress = true;
455 
456     fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>(
457         *crow::connections::systemBus,
458         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
459         "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
460         callback);
461 
462     fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>(
463         *crow::connections::systemBus,
464         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
465         "member='InterfacesAdded',"
466         "path='/xyz/openbmc_project/logging'",
467         std::bind_front(afterUpdateErrorMatcher, asyncResp, url));
468 }
469 
parseSimpleUpdateUrl(std::string imageURI,std::optional<std::string> transferProtocol,crow::Response & res)470 inline std::optional<boost::urls::url> parseSimpleUpdateUrl(
471     std::string imageURI, std::optional<std::string> transferProtocol,
472     crow::Response& res)
473 {
474     if (imageURI.find("://") == std::string::npos)
475     {
476         if (imageURI.starts_with("/"))
477         {
478             messages::actionParameterValueTypeError(
479                 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
480             return std::nullopt;
481         }
482         if (!transferProtocol)
483         {
484             messages::actionParameterValueTypeError(
485                 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
486             return std::nullopt;
487         }
488         // OpenBMC currently only supports HTTPS
489         if (*transferProtocol == "HTTPS")
490         {
491             imageURI = "https://" + imageURI;
492         }
493         else
494         {
495             messages::actionParameterNotSupported(res, "TransferProtocol",
496                                                   *transferProtocol);
497             BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}",
498                              *transferProtocol);
499             return std::nullopt;
500         }
501     }
502 
503     boost::system::result<boost::urls::url> url =
504         boost::urls::parse_absolute_uri(imageURI);
505     if (!url)
506     {
507         messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
508                                                 "UpdateService.SimpleUpdate");
509 
510         return std::nullopt;
511     }
512     url->normalize();
513 
514     if (url->scheme() == "tftp")
515     {
516         if (url->encoded_path().size() < 2)
517         {
518             messages::actionParameterNotSupported(res, "ImageURI",
519                                                   url->buffer());
520             return std::nullopt;
521         }
522     }
523     else if (url->scheme() == "https")
524     {
525         // Empty paths default to "/"
526         if (url->encoded_path().empty())
527         {
528             url->set_encoded_path("/");
529         }
530     }
531     else
532     {
533         messages::actionParameterNotSupported(res, "ImageURI", imageURI);
534         return std::nullopt;
535     }
536 
537     if (url->encoded_path().empty())
538     {
539         messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
540                                                 "UpdateService.SimpleUpdate");
541         return std::nullopt;
542     }
543 
544     return *url;
545 }
546 
doHttpsUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::urls::url_view_base & url)547 inline void doHttpsUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
548                           const boost::urls::url_view_base& url)
549 {
550     messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
551                                           url.buffer());
552 }
553 
handleUpdateServiceSimpleUpdateAction(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)554 inline void handleUpdateServiceSimpleUpdateAction(
555     crow::App& app, const crow::Request& req,
556     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
557 {
558     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
559     {
560         return;
561     }
562 
563     std::optional<std::string> transferProtocol;
564     std::string imageURI;
565 
566     BMCWEB_LOG_DEBUG("Enter UpdateService.SimpleUpdate doPost");
567 
568     // User can pass in both TransferProtocol and ImageURI parameters or
569     // they can pass in just the ImageURI with the transfer protocol
570     // embedded within it.
571     // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin
572     // 2) ImageURI:tftp://1.1.1.1/myfile.bin
573 
574     if (!json_util::readJsonAction( //
575             req, asyncResp->res, //
576             "ImageURI", imageURI, //
577             "TransferProtocol", transferProtocol //
578             ))
579     {
580         BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter");
581         return;
582     }
583 
584     std::optional<boost::urls::url> url =
585         parseSimpleUpdateUrl(imageURI, transferProtocol, asyncResp->res);
586     if (!url)
587     {
588         return;
589     }
590     if (url->scheme() == "https")
591     {
592         doHttpsUpdate(asyncResp, *url);
593     }
594     else
595     {
596         messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
597                                               url->buffer());
598         return;
599     }
600 
601     BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost");
602 }
603 
uploadImageFile(crow::Response & res,std::string_view body)604 inline void uploadImageFile(crow::Response& res, std::string_view body)
605 {
606     std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID());
607 
608     BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string());
609     std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
610                                     std::ofstream::trunc);
611     // set the permission of the file to 640
612     std::filesystem::perms permission =
613         std::filesystem::perms::owner_read | std::filesystem::perms::group_read;
614     std::filesystem::permissions(filepath, permission);
615     out << body;
616 
617     if (out.bad())
618     {
619         messages::internalError(res);
620         cleanUp();
621     }
622 }
623 
624 // Convert the Request Apply Time to the D-Bus value
convertApplyTime(crow::Response & res,const std::string & applyTime,std::string & applyTimeNewVal)625 inline bool convertApplyTime(crow::Response& res, const std::string& applyTime,
626                              std::string& applyTimeNewVal)
627 {
628     if (applyTime == "Immediate")
629     {
630         applyTimeNewVal =
631             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
632     }
633     else if (applyTime == "OnReset")
634     {
635         applyTimeNewVal =
636             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
637     }
638     else
639     {
640         BMCWEB_LOG_WARNING(
641             "ApplyTime value {} is not in the list of acceptable values",
642             applyTime);
643         messages::propertyValueNotInList(res, applyTime, "ApplyTime");
644         return false;
645     }
646     return true;
647 }
648 
setApplyTime(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & applyTime)649 inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
650                          const std::string& applyTime)
651 {
652     std::string applyTimeNewVal;
653     if (!convertApplyTime(asyncResp->res, applyTime, applyTimeNewVal))
654     {
655         return;
656     }
657 
658     setDbusProperty(asyncResp, "ApplyTime", "xyz.openbmc_project.Settings",
659                     sdbusplus::message::object_path(
660                         "/xyz/openbmc_project/software/apply_time"),
661                     "xyz.openbmc_project.Software.ApplyTime",
662                     "RequestedApplyTime", applyTimeNewVal);
663 }
664 
665 struct MultiPartUpdateParameters
666 {
667     std::optional<std::string> applyTime;
668     std::string uploadData;
669     std::vector<std::string> targets;
670 };
671 
672 inline std::optional<std::string>
processUrl(boost::system::result<boost::urls::url_view> & url)673     processUrl(boost::system::result<boost::urls::url_view>& url)
674 {
675     if (!url)
676     {
677         return std::nullopt;
678     }
679     if (crow::utility::readUrlSegments(*url, "redfish", "v1", "Managers",
680                                        BMCWEB_REDFISH_MANAGER_URI_NAME))
681     {
682         return std::make_optional(std::string(BMCWEB_REDFISH_MANAGER_URI_NAME));
683     }
684     if constexpr (!BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
685     {
686         return std::nullopt;
687     }
688     std::string firmwareId;
689     if (!crow::utility::readUrlSegments(*url, "redfish", "v1", "UpdateService",
690                                         "FirmwareInventory",
691                                         std::ref(firmwareId)))
692     {
693         return std::nullopt;
694     }
695 
696     return std::make_optional(firmwareId);
697 }
698 
699 inline std::optional<MultiPartUpdateParameters>
extractMultipartUpdateParameters(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,MultipartParser parser)700     extractMultipartUpdateParameters(
701         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
702         MultipartParser parser)
703 {
704     MultiPartUpdateParameters multiRet;
705     for (FormPart& formpart : parser.mime_fields)
706     {
707         boost::beast::http::fields::const_iterator it =
708             formpart.fields.find("Content-Disposition");
709         if (it == formpart.fields.end())
710         {
711             BMCWEB_LOG_ERROR("Couldn't find Content-Disposition");
712             return std::nullopt;
713         }
714         BMCWEB_LOG_INFO("Parsing value {}", it->value());
715 
716         // The construction parameters of param_list must start with `;`
717         size_t index = it->value().find(';');
718         if (index == std::string::npos)
719         {
720             continue;
721         }
722 
723         for (const auto& param :
724              boost::beast::http::param_list{it->value().substr(index)})
725         {
726             if (param.first != "name" || param.second.empty())
727             {
728                 continue;
729             }
730 
731             if (param.second == "UpdateParameters")
732             {
733                 std::vector<std::string> tempTargets;
734                 nlohmann::json content =
735                     nlohmann::json::parse(formpart.content, nullptr, false);
736                 if (content.is_discarded())
737                 {
738                     return std::nullopt;
739                 }
740                 nlohmann::json::object_t* obj =
741                     content.get_ptr<nlohmann::json::object_t*>();
742                 if (obj == nullptr)
743                 {
744                     messages::propertyValueTypeError(
745                         asyncResp->res, formpart.content, "UpdateParameters");
746                     return std::nullopt;
747                 }
748 
749                 if (!json_util::readJsonObject( //
750                         *obj, asyncResp->res, //
751                         "@Redfish.OperationApplyTime", multiRet.applyTime, //
752                         "Targets", tempTargets //
753                         ))
754                 {
755                     return std::nullopt;
756                 }
757 
758                 for (size_t urlIndex = 0; urlIndex < tempTargets.size();
759                      urlIndex++)
760                 {
761                     const std::string& target = tempTargets[urlIndex];
762                     boost::system::result<boost::urls::url_view> url =
763                         boost::urls::parse_origin_form(target);
764                     auto res = processUrl(url);
765                     if (!res.has_value())
766                     {
767                         messages::propertyValueFormatError(
768                             asyncResp->res, target,
769                             std::format("Targets/{}", urlIndex));
770                         return std::nullopt;
771                     }
772                     multiRet.targets.emplace_back(res.value());
773                 }
774                 if (multiRet.targets.size() != 1)
775                 {
776                     messages::propertyValueFormatError(
777                         asyncResp->res, multiRet.targets, "Targets");
778                     return std::nullopt;
779                 }
780             }
781             else if (param.second == "UpdateFile")
782             {
783                 multiRet.uploadData = std::move(formpart.content);
784             }
785         }
786     }
787 
788     if (multiRet.uploadData.empty())
789     {
790         BMCWEB_LOG_ERROR("Upload data is NULL");
791         messages::propertyMissing(asyncResp->res, "UpdateFile");
792         return std::nullopt;
793     }
794     if (multiRet.targets.empty())
795     {
796         messages::propertyMissing(asyncResp->res, "Targets");
797         return std::nullopt;
798     }
799     return multiRet;
800 }
801 
handleStartUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload payload,const std::string & objectPath,const boost::system::error_code & ec,const sdbusplus::message::object_path & retPath)802 inline void handleStartUpdate(
803     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
804     const std::string& objectPath, const boost::system::error_code& ec,
805     const sdbusplus::message::object_path& retPath)
806 {
807     if (ec)
808     {
809         BMCWEB_LOG_ERROR("error_code = {}", ec);
810         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
811         messages::internalError(asyncResp->res);
812         return;
813     }
814 
815     BMCWEB_LOG_INFO("Call to StartUpdate on {} Success, retPath = {}",
816                     objectPath, retPath.str);
817     createTask(asyncResp, std::move(payload), retPath);
818 }
819 
startUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload payload,const MemoryFileDescriptor & memfd,const std::string & applyTime,const std::string & objectPath,const std::string & serviceName)820 inline void startUpdate(
821     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
822     const MemoryFileDescriptor& memfd, const std::string& applyTime,
823     const std::string& objectPath, const std::string& serviceName)
824 {
825     crow::connections::systemBus->async_method_call(
826         [asyncResp, payload = std::move(payload),
827          objectPath](const boost::system::error_code& ec1,
828                      const sdbusplus::message::object_path& retPath) mutable {
829             handleStartUpdate(asyncResp, std::move(payload), objectPath, ec1,
830                               retPath);
831         },
832         serviceName, objectPath, "xyz.openbmc_project.Software.Update",
833         "StartUpdate", sdbusplus::message::unix_fd(memfd.fd), applyTime);
834 }
835 
getSwInfo(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload payload,const MemoryFileDescriptor & memfd,const std::string & applyTime,const std::string & target,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)836 inline void getSwInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
837                       task::Payload payload, const MemoryFileDescriptor& memfd,
838                       const std::string& applyTime, const std::string& target,
839                       const boost::system::error_code& ec,
840                       const dbus::utility::MapperGetSubTreeResponse& subtree)
841 {
842     using SwInfoMap = std::unordered_map<
843         std::string, std::pair<sdbusplus::message::object_path, std::string>>;
844     SwInfoMap swInfoMap;
845 
846     if (ec)
847     {
848         BMCWEB_LOG_ERROR("error_code = {}", ec);
849         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
850         messages::internalError(asyncResp->res);
851         return;
852     }
853     BMCWEB_LOG_DEBUG("Found {} software version paths", subtree.size());
854 
855     for (const auto& entry : subtree)
856     {
857         sdbusplus::message::object_path path(entry.first);
858         std::string swId = path.filename();
859         swInfoMap.emplace(swId, make_pair(path, entry.second[0].first));
860     }
861 
862     auto swEntry = swInfoMap.find(target);
863     if (swEntry == swInfoMap.end())
864     {
865         BMCWEB_LOG_WARNING("No valid DBus path for Target URI {}", target);
866         messages::propertyValueFormatError(asyncResp->res, target, "Targets");
867         return;
868     }
869 
870     BMCWEB_LOG_DEBUG("Found software version path {} serviceName {}",
871                      swEntry->second.first.str, swEntry->second.second);
872 
873     startUpdate(asyncResp, std::move(payload), memfd, applyTime,
874                 swEntry->second.first.str, swEntry->second.second);
875 }
876 
handleBMCUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload payload,const MemoryFileDescriptor & memfd,const std::string & applyTime,const boost::system::error_code & ec,const dbus::utility::MapperEndPoints & functionalSoftware)877 inline void handleBMCUpdate(
878     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
879     const MemoryFileDescriptor& memfd, const std::string& applyTime,
880     const boost::system::error_code& ec,
881     const dbus::utility::MapperEndPoints& functionalSoftware)
882 {
883     if (ec)
884     {
885         BMCWEB_LOG_ERROR("error_code = {}", ec);
886         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
887         messages::internalError(asyncResp->res);
888         return;
889     }
890     if (functionalSoftware.size() != 1)
891     {
892         BMCWEB_LOG_ERROR("Found {} functional software endpoints",
893                          functionalSoftware.size());
894         messages::internalError(asyncResp->res);
895         return;
896     }
897 
898     startUpdate(asyncResp, std::move(payload), memfd, applyTime,
899                 functionalSoftware[0], "xyz.openbmc_project.Software.Manager");
900 }
901 
processUpdateRequest(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload && payload,std::string_view body,const std::string & applyTime,std::vector<std::string> & targets)902 inline void processUpdateRequest(
903     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
904     task::Payload&& payload, std::string_view body,
905     const std::string& applyTime, std::vector<std::string>& targets)
906 {
907     MemoryFileDescriptor memfd("update-image");
908     if (memfd.fd == -1)
909     {
910         BMCWEB_LOG_ERROR("Failed to create image memfd");
911         messages::internalError(asyncResp->res);
912         return;
913     }
914     if (write(memfd.fd, body.data(), body.length()) !=
915         static_cast<ssize_t>(body.length()))
916     {
917         BMCWEB_LOG_ERROR("Failed to write to image memfd");
918         messages::internalError(asyncResp->res);
919         return;
920     }
921     if (!memfd.rewind())
922     {
923         messages::internalError(asyncResp->res);
924         return;
925     }
926 
927     if (!targets.empty() && targets[0] == BMCWEB_REDFISH_MANAGER_URI_NAME)
928     {
929         dbus::utility::getAssociationEndPoints(
930             "/xyz/openbmc_project/software/bmc/updateable",
931             [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
932              applyTime](
933                 const boost::system::error_code& ec,
934                 const dbus::utility::MapperEndPoints& objectPaths) mutable {
935                 handleBMCUpdate(asyncResp, std::move(payload), memfd, applyTime,
936                                 ec, objectPaths);
937             });
938     }
939     else
940     {
941         constexpr std::array<std::string_view, 1> interfaces = {
942             "xyz.openbmc_project.Software.Version"};
943         dbus::utility::getSubTree(
944             "/xyz/openbmc_project/software", 1, interfaces,
945             [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
946              applyTime, targets](const boost::system::error_code& ec,
947                                  const dbus::utility::MapperGetSubTreeResponse&
948                                      subtree) mutable {
949                 getSwInfo(asyncResp, std::move(payload), memfd, applyTime,
950                           targets[0], ec, subtree);
951             });
952     }
953 }
954 
955 inline void
updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const crow::Request & req,MultipartParser && parser)956     updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
957                            const crow::Request& req, MultipartParser&& parser)
958 {
959     std::optional<MultiPartUpdateParameters> multipart =
960         extractMultipartUpdateParameters(asyncResp, std::move(parser));
961     if (!multipart)
962     {
963         return;
964     }
965     if (!multipart->applyTime)
966     {
967         multipart->applyTime = "OnReset";
968     }
969 
970     if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
971     {
972         std::string applyTimeNewVal;
973         if (!convertApplyTime(asyncResp->res, *multipart->applyTime,
974                               applyTimeNewVal))
975         {
976             return;
977         }
978         task::Payload payload(req);
979 
980         processUpdateRequest(asyncResp, std::move(payload),
981                              multipart->uploadData, applyTimeNewVal,
982                              multipart->targets);
983     }
984     else
985     {
986         setApplyTime(asyncResp, *multipart->applyTime);
987 
988         // Setup callback for when new software detected
989         monitorForSoftwareAvailable(asyncResp, req,
990                                     "/redfish/v1/UpdateService");
991 
992         uploadImageFile(asyncResp->res, multipart->uploadData);
993     }
994 }
995 
doHTTPUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const crow::Request & req)996 inline void doHTTPUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
997                          const crow::Request& req)
998 {
999     if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
1000     {
1001         task::Payload payload(req);
1002         // HTTP push only supports BMC updates (with ApplyTime as immediate) for
1003         // backwards compatibility. Specific component updates will be handled
1004         // through Multipart form HTTP push.
1005         std::vector<std::string> targets;
1006         targets.emplace_back(BMCWEB_REDFISH_MANAGER_URI_NAME);
1007 
1008         processUpdateRequest(
1009             asyncResp, std::move(payload), req.body(),
1010             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate",
1011             targets);
1012     }
1013     else
1014     {
1015         // Setup callback for when new software detected
1016         monitorForSoftwareAvailable(asyncResp, req,
1017                                     "/redfish/v1/UpdateService");
1018 
1019         uploadImageFile(asyncResp->res, req.body());
1020     }
1021 }
1022 
1023 inline void
handleUpdateServicePost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1024     handleUpdateServicePost(App& app, const crow::Request& req,
1025                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1026 {
1027     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1028     {
1029         return;
1030     }
1031     std::string_view contentType = req.getHeaderValue("Content-Type");
1032 
1033     BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType);
1034 
1035     // Make sure that content type is application/octet-stream or
1036     // multipart/form-data
1037     if (bmcweb::asciiIEquals(contentType, "application/octet-stream"))
1038     {
1039         doHTTPUpdate(asyncResp, req);
1040     }
1041     else if (contentType.starts_with("multipart/form-data"))
1042     {
1043         MultipartParser parser;
1044 
1045         ParserError ec = parser.parse(req);
1046         if (ec != ParserError::PARSER_SUCCESS)
1047         {
1048             // handle error
1049             BMCWEB_LOG_ERROR("MIME parse failed, ec : {}",
1050                              static_cast<int>(ec));
1051             messages::internalError(asyncResp->res);
1052             return;
1053         }
1054 
1055         updateMultipartContext(asyncResp, req, std::move(parser));
1056     }
1057     else
1058     {
1059         BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType);
1060         asyncResp->res.result(boost::beast::http::status::bad_request);
1061     }
1062 }
1063 
1064 inline void
handleUpdateServiceGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1065     handleUpdateServiceGet(App& app, const crow::Request& req,
1066                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1067 {
1068     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1069     {
1070         return;
1071     }
1072     asyncResp->res.jsonValue["@odata.type"] =
1073         "#UpdateService.v1_11_1.UpdateService";
1074     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
1075     asyncResp->res.jsonValue["Id"] = "UpdateService";
1076     asyncResp->res.jsonValue["Description"] = "Service for Software Update";
1077     asyncResp->res.jsonValue["Name"] = "Update Service";
1078 
1079     asyncResp->res.jsonValue["HttpPushUri"] =
1080         "/redfish/v1/UpdateService/update";
1081     asyncResp->res.jsonValue["MultipartHttpPushUri"] =
1082         "/redfish/v1/UpdateService/update";
1083 
1084     // UpdateService cannot be disabled
1085     asyncResp->res.jsonValue["ServiceEnabled"] = true;
1086     asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] =
1087         "/redfish/v1/UpdateService/FirmwareInventory";
1088     // Get the MaxImageSizeBytes
1089     asyncResp->res.jsonValue["MaxImageSizeBytes"] =
1090         BMCWEB_HTTP_BODY_LIMIT * 1024 * 1024;
1091 
1092     if constexpr (BMCWEB_REDFISH_ALLOW_SIMPLE_UPDATE)
1093     {
1094         // Update Actions object.
1095         nlohmann::json& updateSvcSimpleUpdate =
1096             asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
1097         updateSvcSimpleUpdate["target"] =
1098             "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
1099 
1100         nlohmann::json::array_t allowed;
1101         allowed.emplace_back(update_service::TransferProtocolType::HTTPS);
1102         updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] =
1103             std::move(allowed);
1104     }
1105 
1106     asyncResp->res
1107         .jsonValue["HttpPushUriOptions"]["HttpPushUriApplyTime"]["ApplyTime"] =
1108         update_service::ApplyTime::Immediate;
1109 }
1110 
handleUpdateServiceFirmwareInventoryCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1111 inline void handleUpdateServiceFirmwareInventoryCollectionGet(
1112     App& app, const crow::Request& req,
1113     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1114 {
1115     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1116     {
1117         return;
1118     }
1119     asyncResp->res.jsonValue["@odata.type"] =
1120         "#SoftwareInventoryCollection.SoftwareInventoryCollection";
1121     asyncResp->res.jsonValue["@odata.id"] =
1122         "/redfish/v1/UpdateService/FirmwareInventory";
1123     asyncResp->res.jsonValue["Name"] = "Software Inventory Collection";
1124     const std::array<const std::string_view, 1> iface = {
1125         "xyz.openbmc_project.Software.Version"};
1126 
1127     redfish::collection_util::getCollectionMembers(
1128         asyncResp,
1129         boost::urls::url("/redfish/v1/UpdateService/FirmwareInventory"), iface,
1130         "/xyz/openbmc_project/software");
1131 }
1132 
1133 /* Fill related item links (i.e. bmc, bios) in for inventory */
getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & purpose)1134 inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1135                             const std::string& purpose)
1136 {
1137     if (purpose == sw_util::bmcPurpose)
1138     {
1139         nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
1140         nlohmann::json::object_t item;
1141         item["@odata.id"] = boost::urls::format(
1142             "/redfish/v1/Managers/{}", BMCWEB_REDFISH_MANAGER_URI_NAME);
1143         relatedItem.emplace_back(std::move(item));
1144         asyncResp->res.jsonValue["RelatedItem@odata.count"] =
1145             relatedItem.size();
1146     }
1147     else if (purpose == sw_util::biosPurpose)
1148     {
1149         nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
1150         nlohmann::json::object_t item;
1151         item["@odata.id"] = std::format("/redfish/v1/Systems/{}/Bios",
1152                                         BMCWEB_REDFISH_SYSTEM_URI_NAME);
1153         relatedItem.emplace_back(std::move(item));
1154         asyncResp->res.jsonValue["RelatedItem@odata.count"] =
1155             relatedItem.size();
1156     }
1157     else
1158     {
1159         BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose);
1160     }
1161 }
1162 
1163 inline void
getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & service,const std::string & path,const std::string & swId)1164     getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1165                        const std::string& service, const std::string& path,
1166                        const std::string& swId)
1167 {
1168     dbus::utility::getAllProperties(
1169         service, path, "xyz.openbmc_project.Software.Version",
1170         [asyncResp,
1171          swId](const boost::system::error_code& ec,
1172                const dbus::utility::DBusPropertiesMap& propertiesList) {
1173             if (ec)
1174             {
1175                 messages::internalError(asyncResp->res);
1176                 return;
1177             }
1178 
1179             const std::string* swInvPurpose = nullptr;
1180             const std::string* version = nullptr;
1181 
1182             const bool success = sdbusplus::unpackPropertiesNoThrow(
1183                 dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose",
1184                 swInvPurpose, "Version", version);
1185 
1186             if (!success)
1187             {
1188                 messages::internalError(asyncResp->res);
1189                 return;
1190             }
1191 
1192             if (swInvPurpose == nullptr)
1193             {
1194                 BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!");
1195                 messages::internalError(asyncResp->res);
1196                 return;
1197             }
1198 
1199             BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose);
1200 
1201             if (version == nullptr)
1202             {
1203                 BMCWEB_LOG_DEBUG("Can't find property \"Version\"!");
1204 
1205                 messages::internalError(asyncResp->res);
1206 
1207                 return;
1208             }
1209             asyncResp->res.jsonValue["Version"] = *version;
1210             asyncResp->res.jsonValue["Id"] = swId;
1211 
1212             // swInvPurpose is of format:
1213             // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
1214             // Translate this to "ABC image"
1215             size_t endDesc = swInvPurpose->rfind('.');
1216             if (endDesc == std::string::npos)
1217             {
1218                 messages::internalError(asyncResp->res);
1219                 return;
1220             }
1221             endDesc++;
1222             if (endDesc >= swInvPurpose->size())
1223             {
1224                 messages::internalError(asyncResp->res);
1225                 return;
1226             }
1227 
1228             std::string formatDesc = swInvPurpose->substr(endDesc);
1229             asyncResp->res.jsonValue["Description"] = formatDesc + " image";
1230             getRelatedItems(asyncResp, *swInvPurpose);
1231         });
1232 }
1233 
handleUpdateServiceFirmwareInventoryGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & param)1234 inline void handleUpdateServiceFirmwareInventoryGet(
1235     App& app, const crow::Request& req,
1236     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1237     const std::string& param)
1238 {
1239     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1240     {
1241         return;
1242     }
1243     std::shared_ptr<std::string> swId = std::make_shared<std::string>(param);
1244 
1245     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
1246         "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId);
1247 
1248     constexpr std::array<std::string_view, 1> interfaces = {
1249         "xyz.openbmc_project.Software.Version"};
1250     dbus::utility::getSubTree(
1251         "/", 0, interfaces,
1252         [asyncResp,
1253          swId](const boost::system::error_code& ec,
1254                const dbus::utility::MapperGetSubTreeResponse& subtree) {
1255             BMCWEB_LOG_DEBUG("doGet callback...");
1256             if (ec)
1257             {
1258                 messages::internalError(asyncResp->res);
1259                 return;
1260             }
1261 
1262             // Ensure we find our input swId, otherwise return an error
1263             bool found = false;
1264             for (const std::pair<std::string,
1265                                  std::vector<std::pair<
1266                                      std::string, std::vector<std::string>>>>&
1267                      obj : subtree)
1268             {
1269                 if (!obj.first.ends_with(*swId))
1270                 {
1271                     continue;
1272                 }
1273 
1274                 if (obj.second.empty())
1275                 {
1276                     continue;
1277                 }
1278 
1279                 found = true;
1280                 sw_util::getSwStatus(asyncResp, swId, obj.second[0].first);
1281                 getSoftwareVersion(asyncResp, obj.second[0].first, obj.first,
1282                                    *swId);
1283             }
1284             if (!found)
1285             {
1286                 BMCWEB_LOG_WARNING("Input swID {} not found!", *swId);
1287                 messages::resourceMissingAtURI(
1288                     asyncResp->res,
1289                     boost::urls::format(
1290                         "/redfish/v1/UpdateService/FirmwareInventory/{}",
1291                         *swId));
1292                 return;
1293             }
1294             asyncResp->res.jsonValue["@odata.type"] =
1295                 "#SoftwareInventory.v1_1_0.SoftwareInventory";
1296             asyncResp->res.jsonValue["Name"] = "Software Inventory";
1297             asyncResp->res.jsonValue["Status"]["HealthRollup"] =
1298                 resource::Health::OK;
1299 
1300             asyncResp->res.jsonValue["Updateable"] = false;
1301             sw_util::getSwUpdatableStatus(asyncResp, swId);
1302         });
1303 }
1304 
requestRoutesUpdateService(App & app)1305 inline void requestRoutesUpdateService(App& app)
1306 {
1307     if constexpr (BMCWEB_REDFISH_ALLOW_SIMPLE_UPDATE)
1308     {
1309         BMCWEB_ROUTE(
1310             app,
1311             "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/")
1312             .privileges(redfish::privileges::postUpdateService)
1313             .methods(boost::beast::http::verb::post)(std::bind_front(
1314                 handleUpdateServiceSimpleUpdateAction, std::ref(app)));
1315     }
1316     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
1317         .privileges(redfish::privileges::getSoftwareInventory)
1318         .methods(boost::beast::http::verb::get)(std::bind_front(
1319             handleUpdateServiceFirmwareInventoryGet, std::ref(app)));
1320 
1321     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
1322         .privileges(redfish::privileges::getUpdateService)
1323         .methods(boost::beast::http::verb::get)(
1324             std::bind_front(handleUpdateServiceGet, std::ref(app)));
1325 
1326     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/")
1327         .privileges(redfish::privileges::postUpdateService)
1328         .methods(boost::beast::http::verb::post)(
1329             std::bind_front(handleUpdateServicePost, std::ref(app)));
1330 
1331     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/")
1332         .privileges(redfish::privileges::getSoftwareInventoryCollection)
1333         .methods(boost::beast::http::verb::get)(std::bind_front(
1334             handleUpdateServiceFirmwareInventoryCollectionGet, std::ref(app)));
1335 }
1336 
1337 } // namespace redfish
1338