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