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