xref: /openbmc/bmcweb/redfish-core/lib/update_service.hpp (revision 67b2e53b2e598bb50fddfa1531e3ab7c2adf2007)
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 inline 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 {}",
288                                          objInfo.size());
289                         if (asyncResp)
290                         {
291                             messages::internalError(asyncResp->res);
292                         }
293                         cleanUp();
294                         return;
295                     }
296                     // cancel timer only when
297                     // xyz.openbmc_project.Software.Activation interface
298                     // is added
299                     fwAvailableTimer = nullptr;
300 
301                     activateImage(objPath.str, objInfo[0].first);
302                     if (asyncResp)
303                     {
304                         createTask(asyncResp, std::move(payload), objPath);
305                     }
306                     fwUpdateInProgress = false;
307                 });
308 
309             break;
310         }
311     }
312 }
313 
314 inline void afterAvailbleTimerAsyncWait(
315     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
316     const boost::system::error_code& ec)
317 {
318     cleanUp();
319     if (ec == boost::asio::error::operation_aborted)
320     {
321         // expected, we were canceled before the timer completed.
322         return;
323     }
324     BMCWEB_LOG_ERROR("Timed out waiting for firmware object being created");
325     BMCWEB_LOG_ERROR("FW image may has already been uploaded to server");
326     if (ec)
327     {
328         BMCWEB_LOG_ERROR("Async_wait failed{}", ec);
329         return;
330     }
331     if (asyncResp)
332     {
333         redfish::messages::internalError(asyncResp->res);
334     }
335 }
336 
337 inline void
338     handleUpdateErrorType(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
339                           const std::string& url, const std::string& type)
340 {
341     // NOLINTBEGIN(bugprone-branch-clone)
342     if (type == "xyz.openbmc_project.Software.Image.Error.UnTarFailure")
343     {
344         messages::missingOrMalformedPart(asyncResp->res);
345     }
346     else if (type ==
347              "xyz.openbmc_project.Software.Image.Error.ManifestFileFailure")
348     {
349         messages::missingOrMalformedPart(asyncResp->res);
350     }
351     else if (type == "xyz.openbmc_project.Software.Image.Error.ImageFailure")
352     {
353         messages::missingOrMalformedPart(asyncResp->res);
354     }
355     else if (type == "xyz.openbmc_project.Software.Version.Error.AlreadyExists")
356     {
357         messages::resourceAlreadyExists(asyncResp->res, "UpdateService",
358                                         "Version", "uploaded version");
359     }
360     else if (type == "xyz.openbmc_project.Software.Image.Error.BusyFailure")
361     {
362         messages::serviceTemporarilyUnavailable(asyncResp->res, url);
363     }
364     else if (type == "xyz.openbmc_project.Software.Version.Error.Incompatible")
365     {
366         messages::internalError(asyncResp->res);
367     }
368     else if (type ==
369              "xyz.openbmc_project.Software.Version.Error.ExpiredAccessKey")
370     {
371         messages::internalError(asyncResp->res);
372     }
373     else if (type ==
374              "xyz.openbmc_project.Software.Version.Error.InvalidSignature")
375     {
376         messages::missingOrMalformedPart(asyncResp->res);
377     }
378     else if (type ==
379                  "xyz.openbmc_project.Software.Image.Error.InternalFailure" ||
380              type == "xyz.openbmc_project.Software.Version.Error.HostFile")
381     {
382         BMCWEB_LOG_ERROR("Software Image Error type={}", type);
383         messages::internalError(asyncResp->res);
384     }
385     else
386     {
387         // Unrelated error types. Ignored
388         BMCWEB_LOG_INFO("Non-Software-related Error type={}. Ignored", type);
389         return;
390     }
391     // NOLINTEND(bugprone-branch-clone)
392     // Clear the timer
393     fwAvailableTimer = nullptr;
394 }
395 
396 inline void
397     afterUpdateErrorMatcher(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
398                             const std::string& url, sdbusplus::message_t& m)
399 {
400     dbus::utility::DBusInterfacesMap interfacesProperties;
401     sdbusplus::message::object_path objPath;
402     m.read(objPath, interfacesProperties);
403     BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
404     for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>&
405              interface : interfacesProperties)
406     {
407         if (interface.first == "xyz.openbmc_project.Logging.Entry")
408         {
409             for (const std::pair<std::string, dbus::utility::DbusVariantType>&
410                      value : interface.second)
411             {
412                 if (value.first != "Message")
413                 {
414                     continue;
415                 }
416                 const std::string* type =
417                     std::get_if<std::string>(&value.second);
418                 if (type == nullptr)
419                 {
420                     // if this was our message, timeout will cover it
421                     return;
422                 }
423                 handleUpdateErrorType(asyncResp, url, *type);
424             }
425         }
426     }
427 }
428 
429 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
430 // then no asyncResp updates will occur
431 inline void monitorForSoftwareAvailable(
432     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
433     const crow::Request& req, const std::string& url,
434     int timeoutTimeSeconds = 25)
435 {
436     // Only allow one FW update at a time
437     if (fwUpdateInProgress)
438     {
439         if (asyncResp)
440         {
441             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
442         }
443         return;
444     }
445 
446     if (req.ioService == nullptr)
447     {
448         messages::internalError(asyncResp->res);
449         return;
450     }
451 
452     fwAvailableTimer =
453         std::make_unique<boost::asio::steady_timer>(*req.ioService);
454 
455     fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds));
456 
457     fwAvailableTimer->async_wait(
458         std::bind_front(afterAvailbleTimerAsyncWait, asyncResp));
459 
460     task::Payload payload(req);
461     auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable {
462         BMCWEB_LOG_DEBUG("Match fired");
463         softwareInterfaceAdded(asyncResp, m, std::move(payload));
464     };
465 
466     fwUpdateInProgress = true;
467 
468     fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>(
469         *crow::connections::systemBus,
470         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
471         "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
472         callback);
473 
474     fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>(
475         *crow::connections::systemBus,
476         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
477         "member='InterfacesAdded',"
478         "path='/xyz/openbmc_project/logging'",
479         std::bind_front(afterUpdateErrorMatcher, asyncResp, url));
480 }
481 
482 inline std::optional<boost::urls::url> parseSimpleUpdateUrl(
483     std::string imageURI, std::optional<std::string> transferProtocol,
484     crow::Response& res)
485 {
486     if (imageURI.find("://") == std::string::npos)
487     {
488         if (imageURI.starts_with("/"))
489         {
490             messages::actionParameterValueTypeError(
491                 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
492             return std::nullopt;
493         }
494         if (!transferProtocol)
495         {
496             messages::actionParameterValueTypeError(
497                 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
498             return std::nullopt;
499         }
500         // OpenBMC currently only supports HTTPS
501         if (*transferProtocol == "HTTPS")
502         {
503             imageURI = "https://" + imageURI;
504         }
505         else
506         {
507             messages::actionParameterNotSupported(res, "TransferProtocol",
508                                                   *transferProtocol);
509             BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}",
510                              *transferProtocol);
511             return std::nullopt;
512         }
513     }
514 
515     boost::system::result<boost::urls::url> url =
516         boost::urls::parse_absolute_uri(imageURI);
517     if (!url)
518     {
519         messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
520                                                 "UpdateService.SimpleUpdate");
521 
522         return std::nullopt;
523     }
524     url->normalize();
525 
526     if (url->scheme() == "tftp")
527     {
528         if (url->encoded_path().size() < 2)
529         {
530             messages::actionParameterNotSupported(res, "ImageURI",
531                                                   url->buffer());
532             return std::nullopt;
533         }
534     }
535     else if (url->scheme() == "https")
536     {
537         // Empty paths default to "/"
538         if (url->encoded_path().empty())
539         {
540             url->set_encoded_path("/");
541         }
542     }
543     else
544     {
545         messages::actionParameterNotSupported(res, "ImageURI", imageURI);
546         return std::nullopt;
547     }
548 
549     if (url->encoded_path().empty())
550     {
551         messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
552                                                 "UpdateService.SimpleUpdate");
553         return std::nullopt;
554     }
555 
556     return *url;
557 }
558 
559 inline void doHttpsUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
560                           const boost::urls::url_view_base& url)
561 {
562     messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
563                                           url.buffer());
564 }
565 
566 inline void handleUpdateServiceSimpleUpdateAction(
567     crow::App& app, const crow::Request& req,
568     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
569 {
570     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
571     {
572         return;
573     }
574 
575     std::optional<std::string> transferProtocol;
576     std::string imageURI;
577 
578     BMCWEB_LOG_DEBUG("Enter UpdateService.SimpleUpdate doPost");
579 
580     // User can pass in both TransferProtocol and ImageURI parameters or
581     // they can pass in just the ImageURI with the transfer protocol
582     // embedded within it.
583     // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin
584     // 2) ImageURI:tftp://1.1.1.1/myfile.bin
585 
586     if (!json_util::readJsonAction( //
587             req, asyncResp->res, //
588             "ImageURI", imageURI, //
589             "TransferProtocol", transferProtocol //
590             ))
591     {
592         BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter");
593         return;
594     }
595 
596     std::optional<boost::urls::url> url =
597         parseSimpleUpdateUrl(imageURI, transferProtocol, asyncResp->res);
598     if (!url)
599     {
600         return;
601     }
602     if (url->scheme() == "https")
603     {
604         doHttpsUpdate(asyncResp, *url);
605     }
606     else
607     {
608         messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
609                                               url->buffer());
610         return;
611     }
612 
613     BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost");
614 }
615 
616 inline void uploadImageFile(crow::Response& res, std::string_view body)
617 {
618     std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID());
619 
620     BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string());
621     std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
622                                     std::ofstream::trunc);
623     // set the permission of the file to 640
624     std::filesystem::perms permission =
625         std::filesystem::perms::owner_read | std::filesystem::perms::group_read;
626     std::filesystem::permissions(filepath, permission);
627     out << body;
628 
629     if (out.bad())
630     {
631         messages::internalError(res);
632         cleanUp();
633     }
634 }
635 
636 // Convert the Request Apply Time to the D-Bus value
637 inline bool convertApplyTime(crow::Response& res, const std::string& applyTime,
638                              std::string& applyTimeNewVal)
639 {
640     if (applyTime == "Immediate")
641     {
642         applyTimeNewVal =
643             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
644     }
645     else if (applyTime == "OnReset")
646     {
647         applyTimeNewVal =
648             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
649     }
650     else
651     {
652         BMCWEB_LOG_WARNING(
653             "ApplyTime value {} is not in the list of acceptable values",
654             applyTime);
655         messages::propertyValueNotInList(res, applyTime, "ApplyTime");
656         return false;
657     }
658     return true;
659 }
660 
661 inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
662                          const std::string& applyTime)
663 {
664     std::string applyTimeNewVal;
665     if (!convertApplyTime(asyncResp->res, applyTime, applyTimeNewVal))
666     {
667         return;
668     }
669 
670     setDbusProperty(asyncResp, "ApplyTime", "xyz.openbmc_project.Settings",
671                     sdbusplus::message::object_path(
672                         "/xyz/openbmc_project/software/apply_time"),
673                     "xyz.openbmc_project.Software.ApplyTime",
674                     "RequestedApplyTime", applyTimeNewVal);
675 }
676 
677 struct MultiPartUpdateParameters
678 {
679     std::optional<std::string> applyTime;
680     std::string uploadData;
681     std::vector<std::string> targets;
682 };
683 
684 inline std::optional<std::string>
685     processUrl(boost::system::result<boost::urls::url_view>& url)
686 {
687     if (!url)
688     {
689         return std::nullopt;
690     }
691     if (crow::utility::readUrlSegments(*url, "redfish", "v1", "Managers",
692                                        BMCWEB_REDFISH_MANAGER_URI_NAME))
693     {
694         return std::make_optional(std::string(BMCWEB_REDFISH_MANAGER_URI_NAME));
695     }
696     if constexpr (!BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
697     {
698         return std::nullopt;
699     }
700     std::string firmwareId;
701     if (!crow::utility::readUrlSegments(*url, "redfish", "v1", "UpdateService",
702                                         "FirmwareInventory",
703                                         std::ref(firmwareId)))
704     {
705         return std::nullopt;
706     }
707 
708     return std::make_optional(firmwareId);
709 }
710 
711 inline std::optional<MultiPartUpdateParameters>
712     extractMultipartUpdateParameters(
713         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
714         MultipartParser parser)
715 {
716     MultiPartUpdateParameters multiRet;
717     for (FormPart& formpart : parser.mime_fields)
718     {
719         boost::beast::http::fields::const_iterator it =
720             formpart.fields.find("Content-Disposition");
721         if (it == formpart.fields.end())
722         {
723             BMCWEB_LOG_ERROR("Couldn't find Content-Disposition");
724             return std::nullopt;
725         }
726         BMCWEB_LOG_INFO("Parsing value {}", it->value());
727 
728         // The construction parameters of param_list must start with `;`
729         size_t index = it->value().find(';');
730         if (index == std::string::npos)
731         {
732             continue;
733         }
734 
735         for (const auto& param :
736              boost::beast::http::param_list{it->value().substr(index)})
737         {
738             if (param.first != "name" || param.second.empty())
739             {
740                 continue;
741             }
742 
743             if (param.second == "UpdateParameters")
744             {
745                 std::vector<std::string> tempTargets;
746                 nlohmann::json content =
747                     nlohmann::json::parse(formpart.content, nullptr, false);
748                 if (content.is_discarded())
749                 {
750                     return std::nullopt;
751                 }
752                 nlohmann::json::object_t* obj =
753                     content.get_ptr<nlohmann::json::object_t*>();
754                 if (obj == nullptr)
755                 {
756                     messages::propertyValueTypeError(
757                         asyncResp->res, formpart.content, "UpdateParameters");
758                     return std::nullopt;
759                 }
760 
761                 if (!json_util::readJsonObject( //
762                         *obj, asyncResp->res, //
763                         "@Redfish.OperationApplyTime", multiRet.applyTime, //
764                         "Targets", tempTargets //
765                         ))
766                 {
767                     return std::nullopt;
768                 }
769 
770                 for (size_t urlIndex = 0; urlIndex < tempTargets.size();
771                      urlIndex++)
772                 {
773                     const std::string& target = tempTargets[urlIndex];
774                     boost::system::result<boost::urls::url_view> url =
775                         boost::urls::parse_origin_form(target);
776                     auto res = processUrl(url);
777                     if (!res.has_value())
778                     {
779                         messages::propertyValueFormatError(
780                             asyncResp->res, target,
781                             std::format("Targets/{}", urlIndex));
782                         return std::nullopt;
783                     }
784                     multiRet.targets.emplace_back(res.value());
785                 }
786                 if (multiRet.targets.size() != 1)
787                 {
788                     messages::propertyValueFormatError(
789                         asyncResp->res, multiRet.targets, "Targets");
790                     return std::nullopt;
791                 }
792             }
793             else if (param.second == "UpdateFile")
794             {
795                 multiRet.uploadData = std::move(formpart.content);
796             }
797         }
798     }
799 
800     if (multiRet.uploadData.empty())
801     {
802         BMCWEB_LOG_ERROR("Upload data is NULL");
803         messages::propertyMissing(asyncResp->res, "UpdateFile");
804         return std::nullopt;
805     }
806     if (multiRet.targets.empty())
807     {
808         messages::propertyMissing(asyncResp->res, "Targets");
809         return std::nullopt;
810     }
811     return multiRet;
812 }
813 
814 inline void handleStartUpdate(
815     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
816     const std::string& objectPath, const boost::system::error_code& ec,
817     const sdbusplus::message::object_path& retPath)
818 {
819     if (ec)
820     {
821         BMCWEB_LOG_ERROR("error_code = {}", ec);
822         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
823         messages::internalError(asyncResp->res);
824         return;
825     }
826 
827     BMCWEB_LOG_INFO("Call to StartUpdate on {} Success, retPath = {}",
828                     objectPath, retPath.str);
829     createTask(asyncResp, std::move(payload), retPath);
830 }
831 
832 inline void startUpdate(
833     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
834     const MemoryFileDescriptor& memfd, const std::string& applyTime,
835     const std::string& objectPath, const std::string& serviceName)
836 {
837     crow::connections::systemBus->async_method_call(
838         [asyncResp, payload = std::move(payload),
839          objectPath](const boost::system::error_code& ec1,
840                      const sdbusplus::message::object_path& retPath) mutable {
841             handleStartUpdate(asyncResp, std::move(payload), objectPath, ec1,
842                               retPath);
843         },
844         serviceName, objectPath, "xyz.openbmc_project.Software.Update",
845         "StartUpdate", sdbusplus::message::unix_fd(memfd.fd), applyTime);
846 }
847 
848 inline void getSwInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
849                       task::Payload payload, const MemoryFileDescriptor& memfd,
850                       const std::string& applyTime, const std::string& target,
851                       const boost::system::error_code& ec,
852                       const dbus::utility::MapperGetSubTreeResponse& subtree)
853 {
854     using SwInfoMap = std::unordered_map<
855         std::string, std::pair<sdbusplus::message::object_path, std::string>>;
856     SwInfoMap swInfoMap;
857 
858     if (ec)
859     {
860         BMCWEB_LOG_ERROR("error_code = {}", ec);
861         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
862         messages::internalError(asyncResp->res);
863         return;
864     }
865     BMCWEB_LOG_DEBUG("Found {} software version paths", subtree.size());
866 
867     for (const auto& entry : subtree)
868     {
869         sdbusplus::message::object_path path(entry.first);
870         std::string swId = path.filename();
871         swInfoMap.emplace(swId, make_pair(path, entry.second[0].first));
872     }
873 
874     auto swEntry = swInfoMap.find(target);
875     if (swEntry == swInfoMap.end())
876     {
877         BMCWEB_LOG_WARNING("No valid DBus path for Target URI {}", target);
878         messages::propertyValueFormatError(asyncResp->res, target, "Targets");
879         return;
880     }
881 
882     BMCWEB_LOG_DEBUG("Found software version path {} serviceName {}",
883                      swEntry->second.first.str, swEntry->second.second);
884 
885     startUpdate(asyncResp, std::move(payload), memfd, applyTime,
886                 swEntry->second.first.str, swEntry->second.second);
887 }
888 
889 inline void handleBMCUpdate(
890     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
891     const MemoryFileDescriptor& memfd, const std::string& applyTime,
892     const boost::system::error_code& ec,
893     const dbus::utility::MapperEndPoints& functionalSoftware)
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     if (functionalSoftware.size() != 1)
903     {
904         BMCWEB_LOG_ERROR("Found {} functional software endpoints",
905                          functionalSoftware.size());
906         messages::internalError(asyncResp->res);
907         return;
908     }
909 
910     startUpdate(asyncResp, std::move(payload), memfd, applyTime,
911                 functionalSoftware[0], "xyz.openbmc_project.Software.Manager");
912 }
913 
914 inline void processUpdateRequest(
915     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
916     task::Payload&& payload, std::string_view body,
917     const std::string& applyTime, std::vector<std::string>& targets)
918 {
919     MemoryFileDescriptor memfd("update-image");
920     if (memfd.fd == -1)
921     {
922         BMCWEB_LOG_ERROR("Failed to create image memfd");
923         messages::internalError(asyncResp->res);
924         return;
925     }
926     if (write(memfd.fd, body.data(), body.length()) !=
927         static_cast<ssize_t>(body.length()))
928     {
929         BMCWEB_LOG_ERROR("Failed to write to image memfd");
930         messages::internalError(asyncResp->res);
931         return;
932     }
933     if (!memfd.rewind())
934     {
935         messages::internalError(asyncResp->res);
936         return;
937     }
938 
939     if (!targets.empty() && targets[0] == BMCWEB_REDFISH_MANAGER_URI_NAME)
940     {
941         dbus::utility::getAssociationEndPoints(
942             "/xyz/openbmc_project/software/bmc/updateable",
943             [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
944              applyTime](
945                 const boost::system::error_code& ec,
946                 const dbus::utility::MapperEndPoints& objectPaths) mutable {
947                 handleBMCUpdate(asyncResp, std::move(payload), memfd, applyTime,
948                                 ec, objectPaths);
949             });
950     }
951     else
952     {
953         constexpr std::array<std::string_view, 1> interfaces = {
954             "xyz.openbmc_project.Software.Version"};
955         dbus::utility::getSubTree(
956             "/xyz/openbmc_project/software", 1, interfaces,
957             [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
958              applyTime, targets](const boost::system::error_code& ec,
959                                  const dbus::utility::MapperGetSubTreeResponse&
960                                      subtree) mutable {
961                 getSwInfo(asyncResp, std::move(payload), memfd, applyTime,
962                           targets[0], ec, subtree);
963             });
964     }
965 }
966 
967 inline void
968     updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
969                            const crow::Request& req, MultipartParser&& parser)
970 {
971     std::optional<MultiPartUpdateParameters> multipart =
972         extractMultipartUpdateParameters(asyncResp, std::move(parser));
973     if (!multipart)
974     {
975         return;
976     }
977     if (!multipart->applyTime)
978     {
979         multipart->applyTime = "OnReset";
980     }
981 
982     if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
983     {
984         std::string applyTimeNewVal;
985         if (!convertApplyTime(asyncResp->res, *multipart->applyTime,
986                               applyTimeNewVal))
987         {
988             return;
989         }
990         task::Payload payload(req);
991 
992         processUpdateRequest(asyncResp, std::move(payload),
993                              multipart->uploadData, applyTimeNewVal,
994                              multipart->targets);
995     }
996     else
997     {
998         setApplyTime(asyncResp, *multipart->applyTime);
999 
1000         // Setup callback for when new software detected
1001         monitorForSoftwareAvailable(asyncResp, req,
1002                                     "/redfish/v1/UpdateService");
1003 
1004         uploadImageFile(asyncResp->res, multipart->uploadData);
1005     }
1006 }
1007 
1008 inline void doHTTPUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1009                          const crow::Request& req)
1010 {
1011     if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
1012     {
1013         task::Payload payload(req);
1014         // HTTP push only supports BMC updates (with ApplyTime as immediate) for
1015         // backwards compatibility. Specific component updates will be handled
1016         // through Multipart form HTTP push.
1017         std::vector<std::string> targets;
1018         targets.emplace_back(BMCWEB_REDFISH_MANAGER_URI_NAME);
1019 
1020         processUpdateRequest(
1021             asyncResp, std::move(payload), req.body(),
1022             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate",
1023             targets);
1024     }
1025     else
1026     {
1027         // Setup callback for when new software detected
1028         monitorForSoftwareAvailable(asyncResp, req,
1029                                     "/redfish/v1/UpdateService");
1030 
1031         uploadImageFile(asyncResp->res, req.body());
1032     }
1033 }
1034 
1035 inline void
1036     handleUpdateServicePost(App& app, const crow::Request& req,
1037                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1038 {
1039     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1040     {
1041         return;
1042     }
1043     std::string_view contentType = req.getHeaderValue("Content-Type");
1044 
1045     BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType);
1046 
1047     // Make sure that content type is application/octet-stream or
1048     // multipart/form-data
1049     if (bmcweb::asciiIEquals(contentType, "application/octet-stream"))
1050     {
1051         doHTTPUpdate(asyncResp, req);
1052     }
1053     else if (contentType.starts_with("multipart/form-data"))
1054     {
1055         MultipartParser parser;
1056 
1057         ParserError ec = parser.parse(req);
1058         if (ec != ParserError::PARSER_SUCCESS)
1059         {
1060             // handle error
1061             BMCWEB_LOG_ERROR("MIME parse failed, ec : {}",
1062                              static_cast<int>(ec));
1063             messages::internalError(asyncResp->res);
1064             return;
1065         }
1066 
1067         updateMultipartContext(asyncResp, req, std::move(parser));
1068     }
1069     else
1070     {
1071         BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType);
1072         asyncResp->res.result(boost::beast::http::status::bad_request);
1073     }
1074 }
1075 
1076 inline void
1077     handleUpdateServiceGet(App& app, const crow::Request& req,
1078                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1079 {
1080     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1081     {
1082         return;
1083     }
1084     asyncResp->res.jsonValue["@odata.type"] =
1085         "#UpdateService.v1_11_1.UpdateService";
1086     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
1087     asyncResp->res.jsonValue["Id"] = "UpdateService";
1088     asyncResp->res.jsonValue["Description"] = "Service for Software Update";
1089     asyncResp->res.jsonValue["Name"] = "Update Service";
1090 
1091     asyncResp->res.jsonValue["HttpPushUri"] =
1092         "/redfish/v1/UpdateService/update";
1093     asyncResp->res.jsonValue["MultipartHttpPushUri"] =
1094         "/redfish/v1/UpdateService/update";
1095 
1096     // UpdateService cannot be disabled
1097     asyncResp->res.jsonValue["ServiceEnabled"] = true;
1098     asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] =
1099         "/redfish/v1/UpdateService/FirmwareInventory";
1100     // Get the MaxImageSizeBytes
1101     asyncResp->res.jsonValue["MaxImageSizeBytes"] =
1102         BMCWEB_HTTP_BODY_LIMIT * 1024 * 1024;
1103 
1104     if constexpr (BMCWEB_REDFISH_ALLOW_SIMPLE_UPDATE)
1105     {
1106         // Update Actions object.
1107         nlohmann::json& updateSvcSimpleUpdate =
1108             asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
1109         updateSvcSimpleUpdate["target"] =
1110             "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
1111 
1112         nlohmann::json::array_t allowed;
1113         allowed.emplace_back(update_service::TransferProtocolType::HTTPS);
1114         updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] =
1115             std::move(allowed);
1116     }
1117 
1118     asyncResp->res
1119         .jsonValue["HttpPushUriOptions"]["HttpPushUriApplyTime"]["ApplyTime"] =
1120         update_service::ApplyTime::Immediate;
1121 }
1122 
1123 inline void handleUpdateServiceFirmwareInventoryCollectionGet(
1124     App& app, const crow::Request& req,
1125     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1126 {
1127     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1128     {
1129         return;
1130     }
1131     asyncResp->res.jsonValue["@odata.type"] =
1132         "#SoftwareInventoryCollection.SoftwareInventoryCollection";
1133     asyncResp->res.jsonValue["@odata.id"] =
1134         "/redfish/v1/UpdateService/FirmwareInventory";
1135     asyncResp->res.jsonValue["Name"] = "Software Inventory Collection";
1136     const std::array<const std::string_view, 1> iface = {
1137         "xyz.openbmc_project.Software.Version"};
1138 
1139     redfish::collection_util::getCollectionMembers(
1140         asyncResp,
1141         boost::urls::url("/redfish/v1/UpdateService/FirmwareInventory"), iface,
1142         "/xyz/openbmc_project/software");
1143 }
1144 
1145 /* Fill related item links (i.e. bmc, bios) in for inventory */
1146 inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1147                             const std::string& purpose)
1148 {
1149     if (purpose == sw_util::bmcPurpose)
1150     {
1151         nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
1152         nlohmann::json::object_t item;
1153         item["@odata.id"] = boost::urls::format(
1154             "/redfish/v1/Managers/{}", BMCWEB_REDFISH_MANAGER_URI_NAME);
1155         relatedItem.emplace_back(std::move(item));
1156         asyncResp->res.jsonValue["RelatedItem@odata.count"] =
1157             relatedItem.size();
1158     }
1159     else if (purpose == sw_util::biosPurpose)
1160     {
1161         nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
1162         nlohmann::json::object_t item;
1163         item["@odata.id"] = std::format("/redfish/v1/Systems/{}/Bios",
1164                                         BMCWEB_REDFISH_SYSTEM_URI_NAME);
1165         relatedItem.emplace_back(std::move(item));
1166         asyncResp->res.jsonValue["RelatedItem@odata.count"] =
1167             relatedItem.size();
1168     }
1169     else
1170     {
1171         BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose);
1172     }
1173 }
1174 
1175 inline void
1176     getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1177                        const std::string& service, const std::string& path,
1178                        const std::string& swId)
1179 {
1180     sdbusplus::asio::getAllProperties(
1181         *crow::connections::systemBus, service, path,
1182         "xyz.openbmc_project.Software.Version",
1183         [asyncResp,
1184          swId](const boost::system::error_code& ec,
1185                const dbus::utility::DBusPropertiesMap& propertiesList) {
1186             if (ec)
1187             {
1188                 messages::internalError(asyncResp->res);
1189                 return;
1190             }
1191 
1192             const std::string* swInvPurpose = nullptr;
1193             const std::string* version = nullptr;
1194 
1195             const bool success = sdbusplus::unpackPropertiesNoThrow(
1196                 dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose",
1197                 swInvPurpose, "Version", version);
1198 
1199             if (!success)
1200             {
1201                 messages::internalError(asyncResp->res);
1202                 return;
1203             }
1204 
1205             if (swInvPurpose == nullptr)
1206             {
1207                 BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!");
1208                 messages::internalError(asyncResp->res);
1209                 return;
1210             }
1211 
1212             BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose);
1213 
1214             if (version == nullptr)
1215             {
1216                 BMCWEB_LOG_DEBUG("Can't find property \"Version\"!");
1217 
1218                 messages::internalError(asyncResp->res);
1219 
1220                 return;
1221             }
1222             asyncResp->res.jsonValue["Version"] = *version;
1223             asyncResp->res.jsonValue["Id"] = swId;
1224 
1225             // swInvPurpose is of format:
1226             // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
1227             // Translate this to "ABC image"
1228             size_t endDesc = swInvPurpose->rfind('.');
1229             if (endDesc == std::string::npos)
1230             {
1231                 messages::internalError(asyncResp->res);
1232                 return;
1233             }
1234             endDesc++;
1235             if (endDesc >= swInvPurpose->size())
1236             {
1237                 messages::internalError(asyncResp->res);
1238                 return;
1239             }
1240 
1241             std::string formatDesc = swInvPurpose->substr(endDesc);
1242             asyncResp->res.jsonValue["Description"] = formatDesc + " image";
1243             getRelatedItems(asyncResp, *swInvPurpose);
1244         });
1245 }
1246 
1247 inline void handleUpdateServiceFirmwareInventoryGet(
1248     App& app, const crow::Request& req,
1249     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1250     const std::string& param)
1251 {
1252     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1253     {
1254         return;
1255     }
1256     std::shared_ptr<std::string> swId = std::make_shared<std::string>(param);
1257 
1258     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
1259         "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId);
1260 
1261     constexpr std::array<std::string_view, 1> interfaces = {
1262         "xyz.openbmc_project.Software.Version"};
1263     dbus::utility::getSubTree(
1264         "/", 0, interfaces,
1265         [asyncResp,
1266          swId](const boost::system::error_code& ec,
1267                const dbus::utility::MapperGetSubTreeResponse& subtree) {
1268             BMCWEB_LOG_DEBUG("doGet callback...");
1269             if (ec)
1270             {
1271                 messages::internalError(asyncResp->res);
1272                 return;
1273             }
1274 
1275             // Ensure we find our input swId, otherwise return an error
1276             bool found = false;
1277             for (const std::pair<std::string,
1278                                  std::vector<std::pair<
1279                                      std::string, std::vector<std::string>>>>&
1280                      obj : subtree)
1281             {
1282                 if (!obj.first.ends_with(*swId))
1283                 {
1284                     continue;
1285                 }
1286 
1287                 if (obj.second.empty())
1288                 {
1289                     continue;
1290                 }
1291 
1292                 found = true;
1293                 sw_util::getSwStatus(asyncResp, swId, obj.second[0].first);
1294                 getSoftwareVersion(asyncResp, obj.second[0].first, obj.first,
1295                                    *swId);
1296             }
1297             if (!found)
1298             {
1299                 BMCWEB_LOG_WARNING("Input swID {} not found!", *swId);
1300                 messages::resourceMissingAtURI(
1301                     asyncResp->res,
1302                     boost::urls::format(
1303                         "/redfish/v1/UpdateService/FirmwareInventory/{}",
1304                         *swId));
1305                 return;
1306             }
1307             asyncResp->res.jsonValue["@odata.type"] =
1308                 "#SoftwareInventory.v1_1_0.SoftwareInventory";
1309             asyncResp->res.jsonValue["Name"] = "Software Inventory";
1310             asyncResp->res.jsonValue["Status"]["HealthRollup"] =
1311                 resource::Health::OK;
1312 
1313             asyncResp->res.jsonValue["Updateable"] = false;
1314             sw_util::getSwUpdatableStatus(asyncResp, swId);
1315         });
1316 }
1317 
1318 inline void requestRoutesUpdateService(App& app)
1319 {
1320     if constexpr (BMCWEB_REDFISH_ALLOW_SIMPLE_UPDATE)
1321     {
1322         BMCWEB_ROUTE(
1323             app,
1324             "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/")
1325             .privileges(redfish::privileges::postUpdateService)
1326             .methods(boost::beast::http::verb::post)(std::bind_front(
1327                 handleUpdateServiceSimpleUpdateAction, std::ref(app)));
1328     }
1329     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
1330         .privileges(redfish::privileges::getSoftwareInventory)
1331         .methods(boost::beast::http::verb::get)(std::bind_front(
1332             handleUpdateServiceFirmwareInventoryGet, std::ref(app)));
1333 
1334     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
1335         .privileges(redfish::privileges::getUpdateService)
1336         .methods(boost::beast::http::verb::get)(
1337             std::bind_front(handleUpdateServiceGet, std::ref(app)));
1338 
1339     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/")
1340         .privileges(redfish::privileges::postUpdateService)
1341         .methods(boost::beast::http::verb::post)(
1342             std::bind_front(handleUpdateServicePost, std::ref(app)));
1343 
1344     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/")
1345         .privileges(redfish::privileges::getSoftwareInventoryCollection)
1346         .methods(boost::beast::http::verb::get)(std::bind_front(
1347             handleUpdateServiceFirmwareInventoryCollectionGet, std::ref(app)));
1348 }
1349 
1350 } // namespace redfish
1351