xref: /openbmc/bmcweb/redfish-core/lib/update_service.hpp (revision f0b59af46a6aa84890d2181b08d4e1af5ce5002f)
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 "multipart_parser.hpp"
23 #include "ossl_random.hpp"
24 #include "query.hpp"
25 #include "registries/privilege_registry.hpp"
26 #include "task.hpp"
27 #include "utils/collection.hpp"
28 #include "utils/dbus_utils.hpp"
29 #include "utils/sw_utils.hpp"
30 
31 #include <boost/system/error_code.hpp>
32 #include <boost/url/format.hpp>
33 #include <sdbusplus/asio/property.hpp>
34 #include <sdbusplus/bus/match.hpp>
35 #include <sdbusplus/unpack_properties.hpp>
36 
37 #include <array>
38 #include <filesystem>
39 #include <optional>
40 #include <string>
41 #include <string_view>
42 
43 namespace redfish
44 {
45 
46 // Match signals added on software path
47 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
48 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateMatcher;
49 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
50 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateErrorMatcher;
51 // Only allow one update at a time
52 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
53 static bool fwUpdateInProgress = false;
54 // Timer for software available
55 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
56 static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer;
57 
58 inline static void cleanUp()
59 {
60     fwUpdateInProgress = false;
61     fwUpdateMatcher = nullptr;
62     fwUpdateErrorMatcher = nullptr;
63 }
64 inline static void activateImage(const std::string& objPath,
65                                  const std::string& service)
66 {
67     BMCWEB_LOG_DEBUG("Activate image for {} {}", objPath, service);
68     sdbusplus::asio::setProperty(
69         *crow::connections::systemBus, service, objPath,
70         "xyz.openbmc_project.Software.Activation", "RequestedActivation",
71         "xyz.openbmc_project.Software.Activation.RequestedActivations.Active",
72         [](const boost::system::error_code& ec) {
73         if (ec)
74         {
75             BMCWEB_LOG_DEBUG("error_code = {}", ec);
76             BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
77         }
78     });
79 }
80 
81 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
82 // then no asyncResp updates will occur
83 static void
84     softwareInterfaceAdded(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
85                            sdbusplus::message_t& m, task::Payload&& payload)
86 {
87     dbus::utility::DBusInterfacesMap interfacesProperties;
88 
89     sdbusplus::message::object_path objPath;
90 
91     m.read(objPath, interfacesProperties);
92 
93     BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
94     for (const auto& interface : interfacesProperties)
95     {
96         BMCWEB_LOG_DEBUG("interface = {}", interface.first);
97 
98         if (interface.first == "xyz.openbmc_project.Software.Activation")
99         {
100             // Retrieve service and activate
101             constexpr std::array<std::string_view, 1> interfaces = {
102                 "xyz.openbmc_project.Software.Activation"};
103             dbus::utility::getDbusObject(
104                 objPath.str, interfaces,
105                 [objPath, asyncResp, payload(std::move(payload))](
106                     const boost::system::error_code& ec,
107                     const std::vector<
108                         std::pair<std::string, std::vector<std::string>>>&
109                         objInfo) mutable {
110                 if (ec)
111                 {
112                     BMCWEB_LOG_DEBUG("error_code = {}", ec);
113                     BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
114                     if (asyncResp)
115                     {
116                         messages::internalError(asyncResp->res);
117                     }
118                     cleanUp();
119                     return;
120                 }
121                 // Ensure we only got one service back
122                 if (objInfo.size() != 1)
123                 {
124                     BMCWEB_LOG_ERROR("Invalid Object Size {}", objInfo.size());
125                     if (asyncResp)
126                     {
127                         messages::internalError(asyncResp->res);
128                     }
129                     cleanUp();
130                     return;
131                 }
132                 // cancel timer only when
133                 // xyz.openbmc_project.Software.Activation interface
134                 // is added
135                 fwAvailableTimer = nullptr;
136 
137                 activateImage(objPath.str, objInfo[0].first);
138                 if (asyncResp)
139                 {
140                     std::shared_ptr<task::TaskData> task =
141                         task::TaskData::createTask(
142                             [](const boost::system::error_code& ec2,
143                                sdbusplus::message_t& msg,
144                                const std::shared_ptr<task::TaskData>&
145                                    taskData) {
146                         if (ec2)
147                         {
148                             return task::completed;
149                         }
150 
151                         std::string iface;
152                         dbus::utility::DBusPropertiesMap values;
153 
154                         std::string index = std::to_string(taskData->index);
155                         msg.read(iface, values);
156 
157                         if (iface == "xyz.openbmc_project.Software.Activation")
158                         {
159                             const std::string* state = nullptr;
160                             for (const auto& property : values)
161                             {
162                                 if (property.first == "Activation")
163                                 {
164                                     state = std::get_if<std::string>(
165                                         &property.second);
166                                     if (state == nullptr)
167                                     {
168                                         taskData->messages.emplace_back(
169                                             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") ||
181                                 state->ends_with("Failed"))
182                             {
183                                 taskData->state = "Exception";
184                                 taskData->status = "Warning";
185                                 taskData->messages.emplace_back(
186                                     messages::taskAborted(index));
187                                 return task::completed;
188                             }
189 
190                             if (state->ends_with("Staged"))
191                             {
192                                 taskData->state = "Stopping";
193                                 taskData->messages.emplace_back(
194                                     messages::taskPaused(index));
195 
196                                 // its staged, set a long timer to
197                                 // allow them time to complete the
198                                 // update (probably cycle the
199                                 // system) if this expires then
200                                 // task will be canceled
201                                 taskData->extendTimer(std::chrono::hours(5));
202                                 return !task::completed;
203                             }
204 
205                             if (state->ends_with("Active"))
206                             {
207                                 taskData->messages.emplace_back(
208                                     messages::taskCompletedOK(index));
209                                 taskData->state = "Completed";
210                                 return task::completed;
211                             }
212                         }
213                         else if (
214                             iface ==
215                             "xyz.openbmc_project.Software.ActivationProgress")
216                         {
217                             const uint8_t* progress = nullptr;
218                             for (const auto& property : values)
219                             {
220                                 if (property.first == "Progress")
221                                 {
222                                     progress =
223                                         std::get_if<uint8_t>(&property.second);
224                                     if (progress == nullptr)
225                                     {
226                                         taskData->messages.emplace_back(
227                                             messages::internalError());
228                                         return task::completed;
229                                     }
230                                 }
231                             }
232 
233                             if (progress == nullptr)
234                             {
235                                 return !task::completed;
236                             }
237                             taskData->percentComplete = *progress;
238                             taskData->messages.emplace_back(
239                                 messages::taskProgressChanged(index,
240                                                               *progress));
241 
242                             // if we're getting status updates it's
243                             // still alive, update timer
244                             taskData->extendTimer(std::chrono::minutes(5));
245                         }
246 
247                         // as firmware update often results in a
248                         // reboot, the task  may never "complete"
249                         // unless it is an error
250 
251                         return !task::completed;
252                     },
253                             "type='signal',interface='org.freedesktop.DBus.Properties',"
254                             "member='PropertiesChanged',path='" +
255                                 objPath.str + "'");
256                     task->startTimer(std::chrono::minutes(5));
257                     task->populateResp(asyncResp->res);
258                     task->payload.emplace(std::move(payload));
259                 }
260                 fwUpdateInProgress = false;
261             });
262 
263             break;
264         }
265     }
266 }
267 
268 inline void afterAvailbleTimerAsyncWait(
269     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
270     const boost::system::error_code& ec)
271 {
272     cleanUp();
273     if (ec == boost::asio::error::operation_aborted)
274     {
275         // expected, we were canceled before the timer completed.
276         return;
277     }
278     BMCWEB_LOG_ERROR("Timed out waiting for firmware object being created");
279     BMCWEB_LOG_ERROR("FW image may has already been uploaded to server");
280     if (ec)
281     {
282         BMCWEB_LOG_ERROR("Async_wait failed{}", ec);
283         return;
284     }
285     if (asyncResp)
286     {
287         redfish::messages::internalError(asyncResp->res);
288     }
289 }
290 
291 inline void
292     handleUpdateErrorType(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
293                           const std::string& url, const std::string& type)
294 {
295     if (type == "xyz.openbmc_project.Software.Image.Error.UnTarFailure")
296     {
297         redfish::messages::invalidUpload(asyncResp->res, url,
298                                          "Invalid archive");
299     }
300     else if (type ==
301              "xyz.openbmc_project.Software.Image.Error.ManifestFileFailure")
302     {
303         redfish::messages::invalidUpload(asyncResp->res, url,
304                                          "Invalid manifest");
305     }
306     else if (type == "xyz.openbmc_project.Software.Image.Error.ImageFailure")
307     {
308         redfish::messages::invalidUpload(asyncResp->res, url,
309                                          "Invalid image format");
310     }
311     else if (type == "xyz.openbmc_project.Software.Version.Error.AlreadyExists")
312     {
313         redfish::messages::invalidUpload(asyncResp->res, url,
314                                          "Image version already exists");
315 
316         redfish::messages::resourceAlreadyExists(
317             asyncResp->res, "UpdateService", "Version", "uploaded version");
318     }
319     else if (type == "xyz.openbmc_project.Software.Image.Error.BusyFailure")
320     {
321         redfish::messages::resourceExhaustion(asyncResp->res, url);
322     }
323     else if (type == "xyz.openbmc_project.Software.Version.Error.Incompatible")
324     {
325         redfish::messages::invalidUpload(asyncResp->res, url,
326                                          "Incompatible image version");
327     }
328     else if (type ==
329              "xyz.openbmc_project.Software.Version.Error.ExpiredAccessKey")
330     {
331         redfish::messages::invalidUpload(asyncResp->res, url,
332                                          "Update Access Key Expired");
333     }
334     else if (type ==
335              "xyz.openbmc_project.Software.Version.Error.InvalidSignature")
336     {
337         redfish::messages::invalidUpload(asyncResp->res, url,
338                                          "Invalid image signature");
339     }
340     else if (type ==
341                  "xyz.openbmc_project.Software.Image.Error.InternalFailure" ||
342              type == "xyz.openbmc_project.Software.Version.Error.HostFile")
343     {
344         BMCWEB_LOG_ERROR("Software Image Error type={}", type);
345         redfish::messages::internalError(asyncResp->res);
346     }
347     else
348     {
349         // Unrelated error types. Ignored
350         BMCWEB_LOG_INFO("Non-Software-related Error type={}. Ignored", type);
351         return;
352     }
353     // Clear the timer
354     fwAvailableTimer = nullptr;
355 }
356 
357 inline void
358     afterUpdateErrorMatcher(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
359                             const std::string& url, sdbusplus::message_t& m)
360 {
361     dbus::utility::DBusInterfacesMap interfacesProperties;
362     sdbusplus::message::object_path objPath;
363     m.read(objPath, interfacesProperties);
364     BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
365     for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>&
366              interface : interfacesProperties)
367     {
368         if (interface.first == "xyz.openbmc_project.Logging.Entry")
369         {
370             for (const std::pair<std::string, dbus::utility::DbusVariantType>&
371                      value : interface.second)
372             {
373                 if (value.first != "Message")
374                 {
375                     continue;
376                 }
377                 const std::string* type =
378                     std::get_if<std::string>(&value.second);
379                 if (type == nullptr)
380                 {
381                     // if this was our message, timeout will cover it
382                     return;
383                 }
384                 handleUpdateErrorType(asyncResp, url, *type);
385             }
386         }
387     }
388 }
389 
390 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
391 // then no asyncResp updates will occur
392 static void monitorForSoftwareAvailable(
393     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
394     const crow::Request& req, const std::string& url,
395     int timeoutTimeSeconds = 25)
396 {
397     // Only allow one FW update at a time
398     if (fwUpdateInProgress)
399     {
400         if (asyncResp)
401         {
402             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
403         }
404         return;
405     }
406 
407     fwAvailableTimer =
408         std::make_unique<boost::asio::steady_timer>(*req.ioService);
409 
410     fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds));
411 
412     fwAvailableTimer->async_wait(
413         std::bind_front(afterAvailbleTimerAsyncWait, asyncResp));
414 
415     task::Payload payload(req);
416     auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable {
417         BMCWEB_LOG_DEBUG("Match fired");
418         softwareInterfaceAdded(asyncResp, m, std::move(payload));
419     };
420 
421     fwUpdateInProgress = true;
422 
423     fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>(
424         *crow::connections::systemBus,
425         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
426         "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
427         callback);
428 
429     fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>(
430         *crow::connections::systemBus,
431         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
432         "member='InterfacesAdded',"
433         "path='/xyz/openbmc_project/logging'",
434         std::bind_front(afterUpdateErrorMatcher, asyncResp, url));
435 }
436 
437 struct TftpUrl
438 {
439     std::string fwFile;
440     std::string tftpServer;
441 };
442 
443 inline std::optional<TftpUrl>
444     parseTftpUrl(std::string imageURI,
445                  std::optional<std::string> transferProtocol,
446                  crow::Response& res)
447 {
448     if (imageURI.find("://") == std::string::npos)
449     {
450         if (imageURI.starts_with("/"))
451         {
452             messages::actionParameterValueTypeError(
453                 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
454             return std::nullopt;
455         }
456         if (!transferProtocol)
457         {
458             messages::actionParameterValueTypeError(
459                 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
460             return std::nullopt;
461         }
462         // OpenBMC currently only supports TFTP
463         if (*transferProtocol != "TFTP")
464         {
465             messages::actionParameterNotSupported(res, "TransferProtocol",
466                                                   *transferProtocol);
467             BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}",
468                              *transferProtocol);
469             return std::nullopt;
470         }
471         imageURI = "tftp://" + imageURI;
472     }
473 
474     boost::system::result<boost::urls::url> url =
475         boost::urls::parse_absolute_uri(imageURI);
476     if (!url)
477     {
478         messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
479                                                 "UpdateService.SimpleUpdate");
480 
481         return std::nullopt;
482     }
483     url->normalize();
484 
485     if (url->scheme() != "tftp")
486     {
487         messages::actionParameterNotSupported(res, "ImageURI", imageURI);
488         return std::nullopt;
489     }
490     std::string path(url->encoded_path());
491     if (path.size() < 2)
492     {
493         messages::actionParameterNotSupported(res, "ImageURI", imageURI);
494         return std::nullopt;
495     }
496     path.erase(0, 1);
497     std::string host(url->encoded_host_and_port());
498     return TftpUrl{path, host};
499 }
500 
501 /**
502  * UpdateServiceActionsSimpleUpdate class supports handle POST method for
503  * SimpleUpdate action.
504  */
505 inline void requestRoutesUpdateServiceActionsSimpleUpdate(App& app)
506 {
507     BMCWEB_ROUTE(
508         app, "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/")
509         .privileges(redfish::privileges::postUpdateService)
510         .methods(boost::beast::http::verb::post)(
511             [&app](const crow::Request& req,
512                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
513         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
514         {
515             return;
516         }
517 
518         std::optional<std::string> transferProtocol;
519         std::string imageURI;
520 
521         BMCWEB_LOG_DEBUG("Enter UpdateService.SimpleUpdate doPost");
522 
523         // User can pass in both TransferProtocol and ImageURI parameters or
524         // they can pass in just the ImageURI with the transfer protocol
525         // embedded within it.
526         // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin
527         // 2) ImageURI:tftp://1.1.1.1/myfile.bin
528 
529         if (!json_util::readJsonAction(req, asyncResp->res, "TransferProtocol",
530                                        transferProtocol, "ImageURI", imageURI))
531         {
532             BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter");
533             return;
534         }
535         std::optional<TftpUrl> ret = parseTftpUrl(imageURI, transferProtocol,
536                                                   asyncResp->res);
537         if (!ret)
538         {
539             return;
540         }
541 
542         BMCWEB_LOG_DEBUG("Server: {} File: {}", ret->tftpServer, ret->fwFile);
543 
544         // Setup callback for when new software detected
545         // Give TFTP 10 minutes to complete
546         monitorForSoftwareAvailable(
547             asyncResp, req,
548             "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate",
549             600);
550 
551         // TFTP can take up to 10 minutes depending on image size and
552         // connection speed. Return to caller as soon as the TFTP operation
553         // has been started. The callback above will ensure the activate
554         // is started once the download has completed
555         redfish::messages::success(asyncResp->res);
556 
557         // Call TFTP service
558         crow::connections::systemBus->async_method_call(
559             [](const boost::system::error_code& ec) {
560             if (ec)
561             {
562                 // messages::internalError(asyncResp->res);
563                 cleanUp();
564                 BMCWEB_LOG_DEBUG("error_code = {}", ec);
565                 BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
566             }
567             else
568             {
569                 BMCWEB_LOG_DEBUG("Call to DownloaViaTFTP Success");
570             }
571         },
572             "xyz.openbmc_project.Software.Download",
573             "/xyz/openbmc_project/software", "xyz.openbmc_project.Common.TFTP",
574             "DownloadViaTFTP", ret->fwFile, ret->tftpServer);
575 
576         BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost");
577     });
578 }
579 
580 inline void uploadImageFile(crow::Response& res, std::string_view body)
581 {
582     std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID());
583 
584     BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string());
585     std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
586                                     std::ofstream::trunc);
587     // set the permission of the file to 640
588     std::filesystem::perms permission = std::filesystem::perms::owner_read |
589                                         std::filesystem::perms::group_read;
590     std::filesystem::permissions(filepath, permission);
591     out << body;
592 
593     if (out.bad())
594     {
595         messages::internalError(res);
596         cleanUp();
597     }
598 }
599 
600 inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
601                          const std::string& applyTime)
602 {
603     std::string applyTimeNewVal;
604     if (applyTime == "Immediate")
605     {
606         applyTimeNewVal =
607             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
608     }
609     else if (applyTime == "OnReset")
610     {
611         applyTimeNewVal =
612             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
613     }
614     else
615     {
616         BMCWEB_LOG_INFO(
617             "ApplyTime value is not in the list of acceptable values");
618         messages::propertyValueNotInList(asyncResp->res, applyTime,
619                                          "ApplyTime");
620         return;
621     }
622 
623     // Set the requested image apply time value
624     sdbusplus::asio::setProperty(
625         *crow::connections::systemBus, "xyz.openbmc_project.Settings",
626         "/xyz/openbmc_project/software/apply_time",
627         "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime",
628         applyTimeNewVal, [asyncResp](const boost::system::error_code& ec) {
629         if (ec)
630         {
631             BMCWEB_LOG_ERROR("D-Bus responses error: {}", ec);
632             messages::internalError(asyncResp->res);
633             return;
634         }
635         messages::success(asyncResp->res);
636     });
637 }
638 
639 inline void
640     updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
641                            const MultipartParser& parser)
642 {
643     const std::string* uploadData = nullptr;
644     std::optional<std::string> applyTime = "OnReset";
645     bool targetFound = false;
646     for (const FormPart& formpart : parser.mime_fields)
647     {
648         boost::beast::http::fields::const_iterator it =
649             formpart.fields.find("Content-Disposition");
650         if (it == formpart.fields.end())
651         {
652             BMCWEB_LOG_ERROR("Couldn't find Content-Disposition");
653             return;
654         }
655         BMCWEB_LOG_INFO("Parsing value {}", it->value());
656 
657         // The construction parameters of param_list must start with `;`
658         size_t index = it->value().find(';');
659         if (index == std::string::npos)
660         {
661             continue;
662         }
663 
664         for (const auto& param :
665              boost::beast::http::param_list{it->value().substr(index)})
666         {
667             if (param.first != "name" || param.second.empty())
668             {
669                 continue;
670             }
671 
672             if (param.second == "UpdateParameters")
673             {
674                 std::vector<std::string> targets;
675                 nlohmann::json content =
676                     nlohmann::json::parse(formpart.content);
677                 nlohmann::json::object_t* obj =
678                     content.get_ptr<nlohmann::json::object_t*>();
679                 if (obj == nullptr)
680                 {
681                     messages::propertyValueFormatError(asyncResp->res, targets,
682                                                        "UpdateParameters");
683                     return;
684                 }
685 
686                 if (!json_util::readJsonObject(
687                         *obj, asyncResp->res, "Targets", targets,
688                         "@Redfish.OperationApplyTime", applyTime))
689                 {
690                     return;
691                 }
692                 if (targets.size() != 1)
693                 {
694                     messages::propertyValueFormatError(asyncResp->res, targets,
695                                                        "Targets");
696                     return;
697                 }
698                 if (targets[0] != "/redfish/v1/Managers/bmc")
699                 {
700                     messages::propertyValueNotInList(asyncResp->res, targets[0],
701                                                      "Targets/0");
702                     return;
703                 }
704                 targetFound = true;
705             }
706             else if (param.second == "UpdateFile")
707             {
708                 uploadData = &(formpart.content);
709             }
710         }
711     }
712 
713     if (uploadData == nullptr)
714     {
715         BMCWEB_LOG_ERROR("Upload data is NULL");
716         messages::propertyMissing(asyncResp->res, "UpdateFile");
717         return;
718     }
719     if (!targetFound)
720     {
721         messages::propertyMissing(asyncResp->res, "targets");
722         return;
723     }
724 
725     setApplyTime(asyncResp, *applyTime);
726 
727     uploadImageFile(asyncResp->res, *uploadData);
728 }
729 
730 inline void
731     handleUpdateServicePost(App& app, const crow::Request& req,
732                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
733 {
734     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
735     {
736         return;
737     }
738     std::string_view contentType = req.getHeaderValue("Content-Type");
739 
740     BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType);
741 
742     // Make sure that content type is application/octet-stream or
743     // multipart/form-data
744     if (bmcweb::asciiIEquals(contentType, "application/octet-stream"))
745     {
746         // Setup callback for when new software detected
747         monitorForSoftwareAvailable(asyncResp, req,
748                                     "/redfish/v1/UpdateService");
749 
750         uploadImageFile(asyncResp->res, req.body());
751     }
752     else if (contentType.starts_with("multipart/form-data"))
753     {
754         MultipartParser parser;
755 
756         // Setup callback for when new software detected
757         monitorForSoftwareAvailable(asyncResp, req,
758                                     "/redfish/v1/UpdateService");
759 
760         ParserError ec = parser.parse(req);
761         if (ec != ParserError::PARSER_SUCCESS)
762         {
763             // handle error
764             BMCWEB_LOG_ERROR("MIME parse failed, ec : {}",
765                              static_cast<int>(ec));
766             messages::internalError(asyncResp->res);
767             return;
768         }
769         updateMultipartContext(asyncResp, parser);
770     }
771     else
772     {
773         BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType);
774         asyncResp->res.result(boost::beast::http::status::bad_request);
775     }
776 }
777 
778 inline void requestRoutesUpdateService(App& app)
779 {
780     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
781         .privileges(redfish::privileges::getUpdateService)
782         .methods(boost::beast::http::verb::get)(
783             [&app](const crow::Request& req,
784                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
785         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
786         {
787             return;
788         }
789         asyncResp->res.jsonValue["@odata.type"] =
790             "#UpdateService.v1_11_1.UpdateService";
791         asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
792         asyncResp->res.jsonValue["Id"] = "UpdateService";
793         asyncResp->res.jsonValue["Description"] = "Service for Software Update";
794         asyncResp->res.jsonValue["Name"] = "Update Service";
795 
796         asyncResp->res.jsonValue["HttpPushUri"] =
797             "/redfish/v1/UpdateService/update";
798         asyncResp->res.jsonValue["MultipartHttpPushUri"] =
799             "/redfish/v1/UpdateService/update";
800 
801         // UpdateService cannot be disabled
802         asyncResp->res.jsonValue["ServiceEnabled"] = true;
803         asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] =
804             "/redfish/v1/UpdateService/FirmwareInventory";
805         // Get the MaxImageSizeBytes
806         asyncResp->res.jsonValue["MaxImageSizeBytes"] =
807             bmcwebHttpReqBodyLimitMb * 1024 * 1024;
808 
809 #ifdef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE
810         // Update Actions object.
811         nlohmann::json& updateSvcSimpleUpdate =
812             asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
813         updateSvcSimpleUpdate["target"] =
814             "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
815         updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] = {
816             "TFTP"};
817 #endif
818         // Get the current ApplyTime value
819         sdbusplus::asio::getProperty<std::string>(
820             *crow::connections::systemBus, "xyz.openbmc_project.Settings",
821             "/xyz/openbmc_project/software/apply_time",
822             "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime",
823             [asyncResp](const boost::system::error_code& ec,
824                         const std::string& applyTime) {
825             if (ec)
826             {
827                 BMCWEB_LOG_DEBUG("DBUS response error {}", ec);
828                 messages::internalError(asyncResp->res);
829                 return;
830             }
831 
832             // Store the ApplyTime Value
833             if (applyTime == "xyz.openbmc_project.Software.ApplyTime."
834                              "RequestedApplyTimes.Immediate")
835             {
836                 asyncResp->res.jsonValue["HttpPushUriOptions"]
837                                         ["HttpPushUriApplyTime"]["ApplyTime"] =
838                     "Immediate";
839             }
840             else if (applyTime == "xyz.openbmc_project.Software.ApplyTime."
841                                   "RequestedApplyTimes.OnReset")
842             {
843                 asyncResp->res.jsonValue["HttpPushUriOptions"]
844                                         ["HttpPushUriApplyTime"]["ApplyTime"] =
845                     "OnReset";
846             }
847         });
848     });
849     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
850         .privileges(redfish::privileges::patchUpdateService)
851         .methods(boost::beast::http::verb::patch)(
852             [&app](const crow::Request& req,
853                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
854         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
855         {
856             return;
857         }
858         BMCWEB_LOG_DEBUG("doPatch...");
859 
860         std::optional<std::string> applyTime;
861         if (!json_util::readJsonPatch(
862                 req, asyncResp->res,
863                 "HttpPushUriOptions/HttpPushUriApplyTime/ApplyTime", applyTime))
864         {
865             return;
866         }
867 
868         if (applyTime)
869         {
870             setApplyTime(asyncResp, *applyTime);
871         }
872     });
873 
874     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/")
875         .privileges(redfish::privileges::postUpdateService)
876         .methods(boost::beast::http::verb::post)(
877             std::bind_front(handleUpdateServicePost, std::ref(app)));
878 }
879 
880 inline void requestRoutesSoftwareInventoryCollection(App& app)
881 {
882     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/")
883         .privileges(redfish::privileges::getSoftwareInventoryCollection)
884         .methods(boost::beast::http::verb::get)(
885             [&app](const crow::Request& req,
886                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
887         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
888         {
889             return;
890         }
891         asyncResp->res.jsonValue["@odata.type"] =
892             "#SoftwareInventoryCollection.SoftwareInventoryCollection";
893         asyncResp->res.jsonValue["@odata.id"] =
894             "/redfish/v1/UpdateService/FirmwareInventory";
895         asyncResp->res.jsonValue["Name"] = "Software Inventory Collection";
896         const std::array<const std::string_view, 1> iface = {
897             "xyz.openbmc_project.Software.Version"};
898 
899         redfish::collection_util::getCollectionMembers(
900             asyncResp,
901             boost::urls::url("/redfish/v1/UpdateService/FirmwareInventory"),
902             iface, "/xyz/openbmc_project/software");
903     });
904 }
905 /* Fill related item links (i.e. bmc, bios) in for inventory */
906 inline static void
907     getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
908                     const std::string& purpose)
909 {
910     if (purpose == sw_util::bmcPurpose)
911     {
912         nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
913         nlohmann::json::object_t item;
914         item["@odata.id"] = "/redfish/v1/Managers/bmc";
915         relatedItem.emplace_back(std::move(item));
916         asyncResp->res.jsonValue["RelatedItem@odata.count"] =
917             relatedItem.size();
918     }
919     else if (purpose == sw_util::biosPurpose)
920     {
921         nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
922         nlohmann::json::object_t item;
923         item["@odata.id"] = "/redfish/v1/Systems/system/Bios";
924         relatedItem.emplace_back(std::move(item));
925         asyncResp->res.jsonValue["RelatedItem@odata.count"] =
926             relatedItem.size();
927     }
928     else
929     {
930         BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose);
931     }
932 }
933 
934 inline void
935     getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
936                        const std::string& service, const std::string& path,
937                        const std::string& swId)
938 {
939     sdbusplus::asio::getAllProperties(
940         *crow::connections::systemBus, service, path,
941         "xyz.openbmc_project.Software.Version",
942         [asyncResp,
943          swId](const boost::system::error_code& ec,
944                const dbus::utility::DBusPropertiesMap& propertiesList) {
945         if (ec)
946         {
947             messages::internalError(asyncResp->res);
948             return;
949         }
950 
951         const std::string* swInvPurpose = nullptr;
952         const std::string* version = nullptr;
953 
954         const bool success = sdbusplus::unpackPropertiesNoThrow(
955             dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose",
956             swInvPurpose, "Version", version);
957 
958         if (!success)
959         {
960             messages::internalError(asyncResp->res);
961             return;
962         }
963 
964         if (swInvPurpose == nullptr)
965         {
966             BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!");
967             messages::internalError(asyncResp->res);
968             return;
969         }
970 
971         BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose);
972 
973         if (version == nullptr)
974         {
975             BMCWEB_LOG_DEBUG("Can't find property \"Version\"!");
976 
977             messages::internalError(asyncResp->res);
978 
979             return;
980         }
981         asyncResp->res.jsonValue["Version"] = *version;
982         asyncResp->res.jsonValue["Id"] = swId;
983 
984         // swInvPurpose is of format:
985         // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
986         // Translate this to "ABC image"
987         size_t endDesc = swInvPurpose->rfind('.');
988         if (endDesc == std::string::npos)
989         {
990             messages::internalError(asyncResp->res);
991             return;
992         }
993         endDesc++;
994         if (endDesc >= swInvPurpose->size())
995         {
996             messages::internalError(asyncResp->res);
997             return;
998         }
999 
1000         std::string formatDesc = swInvPurpose->substr(endDesc);
1001         asyncResp->res.jsonValue["Description"] = formatDesc + " image";
1002         getRelatedItems(asyncResp, *swInvPurpose);
1003     });
1004 }
1005 
1006 inline void requestRoutesSoftwareInventory(App& app)
1007 {
1008     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
1009         .privileges(redfish::privileges::getSoftwareInventory)
1010         .methods(boost::beast::http::verb::get)(
1011             [&app](const crow::Request& req,
1012                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1013                    const std::string& param) {
1014         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1015         {
1016             return;
1017         }
1018         std::shared_ptr<std::string> swId =
1019             std::make_shared<std::string>(param);
1020 
1021         asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
1022             "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId);
1023 
1024         constexpr std::array<std::string_view, 1> interfaces = {
1025             "xyz.openbmc_project.Software.Version"};
1026         dbus::utility::getSubTree(
1027             "/", 0, interfaces,
1028             [asyncResp,
1029              swId](const boost::system::error_code& ec,
1030                    const dbus::utility::MapperGetSubTreeResponse& subtree) {
1031             BMCWEB_LOG_DEBUG("doGet callback...");
1032             if (ec)
1033             {
1034                 messages::internalError(asyncResp->res);
1035                 return;
1036             }
1037 
1038             // Ensure we find our input swId, otherwise return an error
1039             bool found = false;
1040             for (const std::pair<std::string,
1041                                  std::vector<std::pair<
1042                                      std::string, std::vector<std::string>>>>&
1043                      obj : subtree)
1044             {
1045                 if (!obj.first.ends_with(*swId))
1046                 {
1047                     continue;
1048                 }
1049 
1050                 if (obj.second.empty())
1051                 {
1052                     continue;
1053                 }
1054 
1055                 found = true;
1056                 sw_util::getSwStatus(asyncResp, swId, obj.second[0].first);
1057                 getSoftwareVersion(asyncResp, obj.second[0].first, obj.first,
1058                                    *swId);
1059             }
1060             if (!found)
1061             {
1062                 BMCWEB_LOG_WARNING("Input swID {} not found!", *swId);
1063                 messages::resourceMissingAtURI(
1064                     asyncResp->res,
1065                     boost::urls::format(
1066                         "/redfish/v1/UpdateService/FirmwareInventory/{}",
1067                         *swId));
1068                 return;
1069             }
1070             asyncResp->res.jsonValue["@odata.type"] =
1071                 "#SoftwareInventory.v1_1_0.SoftwareInventory";
1072             asyncResp->res.jsonValue["Name"] = "Software Inventory";
1073             asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
1074 
1075             asyncResp->res.jsonValue["Updateable"] = false;
1076             sw_util::getSwUpdatableStatus(asyncResp, swId);
1077         });
1078     });
1079 }
1080 
1081 } // namespace redfish
1082