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