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::DBusInteracesMap 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 cancelled
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 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
268 // then no asyncResp updates will occur
269 static void monitorForSoftwareAvailable(
270     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
271     const crow::Request& req, const std::string& url,
272     int timeoutTimeSeconds = 25)
273 {
274     // Only allow one FW update at a time
275     if (fwUpdateInProgress)
276     {
277         if (asyncResp)
278         {
279             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
280         }
281         return;
282     }
283 
284     fwAvailableTimer =
285         std::make_unique<boost::asio::steady_timer>(*req.ioService);
286 
287     fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds));
288 
289     fwAvailableTimer->async_wait(
290         [asyncResp](const boost::system::error_code& ec) {
291         cleanUp();
292         if (ec == boost::asio::error::operation_aborted)
293         {
294             // expected, we were canceled before the timer completed.
295             return;
296         }
297         BMCWEB_LOG_ERROR("Timed out waiting for firmware object being created");
298         BMCWEB_LOG_ERROR("FW image may has already been uploaded to server");
299         if (ec)
300         {
301             BMCWEB_LOG_ERROR("Async_wait failed{}", ec);
302             return;
303         }
304         if (asyncResp)
305         {
306             redfish::messages::internalError(asyncResp->res);
307         }
308     });
309     task::Payload payload(req);
310     auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable {
311         BMCWEB_LOG_DEBUG("Match fired");
312         softwareInterfaceAdded(asyncResp, m, std::move(payload));
313     };
314 
315     fwUpdateInProgress = true;
316 
317     fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>(
318         *crow::connections::systemBus,
319         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
320         "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
321         callback);
322 
323     fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>(
324         *crow::connections::systemBus,
325         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
326         "member='InterfacesAdded',"
327         "path='/xyz/openbmc_project/logging'",
328         [asyncResp, url](sdbusplus::message_t& m) {
329         std::vector<std::pair<std::string, dbus::utility::DBusPropertiesMap>>
330             interfacesProperties;
331         sdbusplus::message::object_path objPath;
332         m.read(objPath, interfacesProperties);
333         BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
334         for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>&
335                  interface : interfacesProperties)
336         {
337             if (interface.first == "xyz.openbmc_project.Logging.Entry")
338             {
339                 for (const std::pair<std::string,
340                                      dbus::utility::DbusVariantType>& value :
341                      interface.second)
342                 {
343                     if (value.first != "Message")
344                     {
345                         continue;
346                     }
347                     const std::string* type =
348                         std::get_if<std::string>(&value.second);
349                     if (type == nullptr)
350                     {
351                         // if this was our message, timeout will cover it
352                         return;
353                     }
354                     fwAvailableTimer = nullptr;
355                     if (*type ==
356                         "xyz.openbmc_project.Software.Image.Error.UnTarFailure")
357                     {
358                         redfish::messages::invalidUpload(asyncResp->res, url,
359                                                          "Invalid archive");
360                     }
361                     else if (*type ==
362                              "xyz.openbmc_project.Software.Image.Error."
363                              "ManifestFileFailure")
364                     {
365                         redfish::messages::invalidUpload(asyncResp->res, url,
366                                                          "Invalid manifest");
367                     }
368                     else if (
369                         *type ==
370                         "xyz.openbmc_project.Software.Image.Error.ImageFailure")
371                     {
372                         redfish::messages::invalidUpload(
373                             asyncResp->res, url, "Invalid image format");
374                     }
375                     else if (
376                         *type ==
377                         "xyz.openbmc_project.Software.Version.Error.AlreadyExists")
378                     {
379                         redfish::messages::invalidUpload(
380                             asyncResp->res, url,
381                             "Image version already exists");
382 
383                         redfish::messages::resourceAlreadyExists(
384                             asyncResp->res, "UpdateService", "Version",
385                             "uploaded version");
386                     }
387                     else if (
388                         *type ==
389                         "xyz.openbmc_project.Software.Image.Error.BusyFailure")
390                     {
391                         redfish::messages::resourceExhaustion(asyncResp->res,
392                                                               url);
393                     }
394                     else
395                     {
396                         redfish::messages::internalError(asyncResp->res);
397                     }
398                 }
399             }
400         }
401         });
402 }
403 
404 /**
405  * UpdateServiceActionsSimpleUpdate class supports handle POST method for
406  * SimpleUpdate action.
407  */
408 inline void requestRoutesUpdateServiceActionsSimpleUpdate(App& app)
409 {
410     BMCWEB_ROUTE(
411         app, "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/")
412         .privileges(redfish::privileges::postUpdateService)
413         .methods(boost::beast::http::verb::post)(
414             [&app](const crow::Request& req,
415                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
416         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
417         {
418             return;
419         }
420 
421         std::optional<std::string> transferProtocol;
422         std::string imageURI;
423 
424         BMCWEB_LOG_DEBUG("Enter UpdateService.SimpleUpdate doPost");
425 
426         // User can pass in both TransferProtocol and ImageURI parameters or
427         // they can pass in just the ImageURI with the transfer protocol
428         // embedded within it.
429         // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin
430         // 2) ImageURI:tftp://1.1.1.1/myfile.bin
431 
432         if (!json_util::readJsonAction(req, asyncResp->res, "TransferProtocol",
433                                        transferProtocol, "ImageURI", imageURI))
434         {
435             BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter");
436             return;
437         }
438         if (!transferProtocol)
439         {
440             // Must be option 2
441             // Verify ImageURI has transfer protocol in it
442             size_t separator = imageURI.find(':');
443             if ((separator == std::string::npos) ||
444                 ((separator + 1) > imageURI.size()))
445             {
446                 messages::actionParameterValueTypeError(
447                     asyncResp->res, imageURI, "ImageURI",
448                     "UpdateService.SimpleUpdate");
449                 BMCWEB_LOG_ERROR("ImageURI missing transfer protocol: {}",
450                                  imageURI);
451                 return;
452             }
453             transferProtocol = imageURI.substr(0, separator);
454             // Ensure protocol is upper case for a common comparison path
455             // below
456             boost::to_upper(*transferProtocol);
457             BMCWEB_LOG_DEBUG("Encoded transfer protocol {}", *transferProtocol);
458 
459             // Adjust imageURI to not have the protocol on it for parsing
460             // below
461             // ex. tftp://1.1.1.1/myfile.bin -> 1.1.1.1/myfile.bin
462             imageURI = imageURI.substr(separator + 3);
463             BMCWEB_LOG_DEBUG("Adjusted imageUri {}", imageURI);
464         }
465 
466         // OpenBMC currently only supports TFTP
467         if (*transferProtocol != "TFTP")
468         {
469             messages::actionParameterNotSupported(asyncResp->res,
470                                                   "TransferProtocol",
471                                                   "UpdateService.SimpleUpdate");
472             BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}",
473                              *transferProtocol);
474             return;
475         }
476 
477         // Format should be <IP or Hostname>/<file> for imageURI
478         size_t separator = imageURI.find('/');
479         if ((separator == std::string::npos) ||
480             ((separator + 1) > imageURI.size()))
481         {
482             messages::actionParameterValueTypeError(
483                 asyncResp->res, imageURI, "ImageURI",
484                 "UpdateService.SimpleUpdate");
485             BMCWEB_LOG_ERROR("Invalid ImageURI: {}", imageURI);
486             return;
487         }
488 
489         std::string tftpServer = imageURI.substr(0, separator);
490         std::string fwFile = imageURI.substr(separator + 1);
491         BMCWEB_LOG_DEBUG("Server: {}{}", tftpServer + " File: ", fwFile);
492 
493         // Setup callback for when new software detected
494         // Give TFTP 10 minutes to complete
495         monitorForSoftwareAvailable(
496             asyncResp, req,
497             "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate",
498             600);
499 
500         // TFTP can take up to 10 minutes depending on image size and
501         // connection speed. Return to caller as soon as the TFTP operation
502         // has been started. The callback above will ensure the activate
503         // is started once the download has completed
504         redfish::messages::success(asyncResp->res);
505 
506         // Call TFTP service
507         crow::connections::systemBus->async_method_call(
508             [](const boost::system::error_code& ec) {
509             if (ec)
510             {
511                 // messages::internalError(asyncResp->res);
512                 cleanUp();
513                 BMCWEB_LOG_DEBUG("error_code = {}", ec);
514                 BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
515             }
516             else
517             {
518                 BMCWEB_LOG_DEBUG("Call to DownloaViaTFTP Success");
519             }
520             },
521             "xyz.openbmc_project.Software.Download",
522             "/xyz/openbmc_project/software", "xyz.openbmc_project.Common.TFTP",
523             "DownloadViaTFTP", fwFile, tftpServer);
524 
525         BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost");
526         });
527 }
528 
529 inline void uploadImageFile(crow::Response& res, std::string_view body)
530 {
531     std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID());
532 
533     BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string());
534     std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
535                                     std::ofstream::trunc);
536     // set the permission of the file to 640
537     std::filesystem::perms permission = std::filesystem::perms::owner_read |
538                                         std::filesystem::perms::group_read;
539     std::filesystem::permissions(filepath, permission);
540     out << body;
541 
542     if (out.bad())
543     {
544         messages::internalError(res);
545         cleanUp();
546     }
547 }
548 
549 inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
550                          const std::string& applyTime)
551 {
552     std::string applyTimeNewVal;
553     if (applyTime == "Immediate")
554     {
555         applyTimeNewVal =
556             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
557     }
558     else if (applyTime == "OnReset")
559     {
560         applyTimeNewVal =
561             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
562     }
563     else
564     {
565         BMCWEB_LOG_INFO(
566             "ApplyTime value is not in the list of acceptable values");
567         messages::propertyValueNotInList(asyncResp->res, applyTime,
568                                          "ApplyTime");
569         return;
570     }
571 
572     // Set the requested image apply time value
573     sdbusplus::asio::setProperty(
574         *crow::connections::systemBus, "xyz.openbmc_project.Settings",
575         "/xyz/openbmc_project/software/apply_time",
576         "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime",
577         applyTimeNewVal, [asyncResp](const boost::system::error_code& ec) {
578             if (ec)
579             {
580                 BMCWEB_LOG_ERROR("D-Bus responses error: {}", ec);
581                 messages::internalError(asyncResp->res);
582                 return;
583             }
584             messages::success(asyncResp->res);
585         });
586 }
587 
588 inline void
589     updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
590                            const MultipartParser& parser)
591 {
592     const std::string* uploadData = nullptr;
593     std::optional<std::string> applyTime = "OnReset";
594     bool targetFound = false;
595     for (const FormPart& formpart : parser.mime_fields)
596     {
597         boost::beast::http::fields::const_iterator it =
598             formpart.fields.find("Content-Disposition");
599         if (it == formpart.fields.end())
600         {
601             BMCWEB_LOG_ERROR("Couldn't find Content-Disposition");
602             return;
603         }
604         BMCWEB_LOG_INFO("Parsing value {}", it->value());
605 
606         // The construction parameters of param_list must start with `;`
607         size_t index = it->value().find(';');
608         if (index == std::string::npos)
609         {
610             continue;
611         }
612 
613         for (const auto& param :
614              boost::beast::http::param_list{it->value().substr(index)})
615         {
616             if (param.first != "name" || param.second.empty())
617             {
618                 continue;
619             }
620 
621             if (param.second == "UpdateParameters")
622             {
623                 std::vector<std::string> targets;
624                 nlohmann::json content =
625                     nlohmann::json::parse(formpart.content);
626                 if (!json_util::readJson(content, asyncResp->res, "Targets",
627                                          targets, "@Redfish.OperationApplyTime",
628                                          applyTime))
629                 {
630                     return;
631                 }
632                 if (targets.size() != 1)
633                 {
634                     messages::propertyValueFormatError(asyncResp->res,
635                                                        "Targets", "");
636                     return;
637                 }
638                 if (targets[0] != "/redfish/v1/Managers/bmc")
639                 {
640                     messages::propertyValueNotInList(asyncResp->res,
641                                                      "Targets/0", targets[0]);
642                     return;
643                 }
644                 targetFound = true;
645             }
646             else if (param.second == "UpdateFile")
647             {
648                 uploadData = &(formpart.content);
649             }
650         }
651     }
652 
653     if (uploadData == nullptr)
654     {
655         BMCWEB_LOG_ERROR("Upload data is NULL");
656         messages::propertyMissing(asyncResp->res, "UpdateFile");
657         return;
658     }
659     if (!targetFound)
660     {
661         messages::propertyMissing(asyncResp->res, "targets");
662         return;
663     }
664 
665     setApplyTime(asyncResp, *applyTime);
666 
667     uploadImageFile(asyncResp->res, *uploadData);
668 }
669 
670 inline void
671     handleUpdateServicePost(App& app, const crow::Request& req,
672                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
673 {
674     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
675     {
676         return;
677     }
678     std::string_view contentType = req.getHeaderValue("Content-Type");
679 
680     BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType);
681 
682     // Make sure that content type is application/octet-stream or
683     // multipart/form-data
684     if (boost::iequals(contentType, "application/octet-stream"))
685     {
686         // Setup callback for when new software detected
687         monitorForSoftwareAvailable(asyncResp, req,
688                                     "/redfish/v1/UpdateService");
689 
690         uploadImageFile(asyncResp->res, req.body());
691     }
692     else if (contentType.starts_with("multipart/form-data"))
693     {
694         MultipartParser parser;
695 
696         // Setup callback for when new software detected
697         monitorForSoftwareAvailable(asyncResp, req,
698                                     "/redfish/v1/UpdateService");
699 
700         ParserError ec = parser.parse(req);
701         if (ec != ParserError::PARSER_SUCCESS)
702         {
703             // handle error
704             BMCWEB_LOG_ERROR("MIME parse failed, ec : {}",
705                              static_cast<int>(ec));
706             messages::internalError(asyncResp->res);
707             return;
708         }
709         updateMultipartContext(asyncResp, parser);
710     }
711     else
712     {
713         BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType);
714         asyncResp->res.result(boost::beast::http::status::bad_request);
715     }
716 }
717 
718 inline void requestRoutesUpdateService(App& app)
719 {
720     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
721         .privileges(redfish::privileges::getUpdateService)
722         .methods(boost::beast::http::verb::get)(
723             [&app](const crow::Request& req,
724                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
725         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
726         {
727             return;
728         }
729         asyncResp->res.jsonValue["@odata.type"] =
730             "#UpdateService.v1_11_1.UpdateService";
731         asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
732         asyncResp->res.jsonValue["Id"] = "UpdateService";
733         asyncResp->res.jsonValue["Description"] = "Service for Software Update";
734         asyncResp->res.jsonValue["Name"] = "Update Service";
735 
736         asyncResp->res.jsonValue["HttpPushUri"] =
737             "/redfish/v1/UpdateService/update";
738         asyncResp->res.jsonValue["MultipartHttpPushUri"] =
739             "/redfish/v1/UpdateService/update";
740 
741         // UpdateService cannot be disabled
742         asyncResp->res.jsonValue["ServiceEnabled"] = true;
743         asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] =
744             "/redfish/v1/UpdateService/FirmwareInventory";
745         // Get the MaxImageSizeBytes
746         asyncResp->res.jsonValue["MaxImageSizeBytes"] =
747             bmcwebHttpReqBodyLimitMb * 1024 * 1024;
748 
749 #ifdef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE
750         // Update Actions object.
751         nlohmann::json& updateSvcSimpleUpdate =
752             asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
753         updateSvcSimpleUpdate["target"] =
754             "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
755         updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] = {
756             "TFTP"};
757 #endif
758         // Get the current ApplyTime value
759         sdbusplus::asio::getProperty<std::string>(
760             *crow::connections::systemBus, "xyz.openbmc_project.Settings",
761             "/xyz/openbmc_project/software/apply_time",
762             "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime",
763             [asyncResp](const boost::system::error_code& ec,
764                         const std::string& applyTime) {
765             if (ec)
766             {
767                 BMCWEB_LOG_DEBUG("DBUS response error {}", ec);
768                 messages::internalError(asyncResp->res);
769                 return;
770             }
771 
772             // Store the ApplyTime Value
773             if (applyTime == "xyz.openbmc_project.Software.ApplyTime."
774                              "RequestedApplyTimes.Immediate")
775             {
776                 asyncResp->res.jsonValue["HttpPushUriOptions"]
777                                         ["HttpPushUriApplyTime"]["ApplyTime"] =
778                     "Immediate";
779             }
780             else if (applyTime == "xyz.openbmc_project.Software.ApplyTime."
781                                   "RequestedApplyTimes.OnReset")
782             {
783                 asyncResp->res.jsonValue["HttpPushUriOptions"]
784                                         ["HttpPushUriApplyTime"]["ApplyTime"] =
785                     "OnReset";
786             }
787             });
788         });
789     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
790         .privileges(redfish::privileges::patchUpdateService)
791         .methods(boost::beast::http::verb::patch)(
792             [&app](const crow::Request& req,
793                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
794         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
795         {
796             return;
797         }
798         BMCWEB_LOG_DEBUG("doPatch...");
799 
800         std::optional<nlohmann::json> pushUriOptions;
801         if (!json_util::readJsonPatch(req, asyncResp->res, "HttpPushUriOptions",
802                                       pushUriOptions))
803         {
804             return;
805         }
806 
807         if (pushUriOptions)
808         {
809             std::optional<nlohmann::json> pushUriApplyTime;
810             if (!json_util::readJson(*pushUriOptions, asyncResp->res,
811                                      "HttpPushUriApplyTime", pushUriApplyTime))
812             {
813                 return;
814             }
815 
816             if (pushUriApplyTime)
817             {
818                 std::optional<std::string> applyTime;
819                 if (!json_util::readJson(*pushUriApplyTime, asyncResp->res,
820                                          "ApplyTime", applyTime))
821                 {
822                     return;
823                 }
824 
825                 if (applyTime)
826                 {
827                     setApplyTime(asyncResp, *applyTime);
828                 }
829             }
830         }
831         });
832 
833     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/")
834         .privileges(redfish::privileges::postUpdateService)
835         .methods(boost::beast::http::verb::post)(
836             std::bind_front(handleUpdateServicePost, std::ref(app)));
837 }
838 
839 inline void requestRoutesSoftwareInventoryCollection(App& app)
840 {
841     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/")
842         .privileges(redfish::privileges::getSoftwareInventoryCollection)
843         .methods(boost::beast::http::verb::get)(
844             [&app](const crow::Request& req,
845                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
846         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
847         {
848             return;
849         }
850         asyncResp->res.jsonValue["@odata.type"] =
851             "#SoftwareInventoryCollection.SoftwareInventoryCollection";
852         asyncResp->res.jsonValue["@odata.id"] =
853             "/redfish/v1/UpdateService/FirmwareInventory";
854         asyncResp->res.jsonValue["Name"] = "Software Inventory Collection";
855         const std::array<const std::string_view, 1> iface = {
856             "xyz.openbmc_project.Software.Version"};
857 
858         redfish::collection_util::getCollectionMembers(
859             asyncResp,
860             boost::urls::url("/redfish/v1/UpdateService/FirmwareInventory"),
861             iface, "/xyz/openbmc_project/software");
862         });
863 }
864 /* Fill related item links (i.e. bmc, bios) in for inventory */
865 inline static void
866     getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
867                     const std::string& purpose)
868 {
869     if (purpose == sw_util::bmcPurpose)
870     {
871         nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
872         nlohmann::json::object_t item;
873         item["@odata.id"] = "/redfish/v1/Managers/bmc";
874         relatedItem.emplace_back(std::move(item));
875         asyncResp->res.jsonValue["RelatedItem@odata.count"] =
876             relatedItem.size();
877     }
878     else if (purpose == sw_util::biosPurpose)
879     {
880         nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
881         nlohmann::json::object_t item;
882         item["@odata.id"] = "/redfish/v1/Systems/system/Bios";
883         relatedItem.emplace_back(std::move(item));
884         asyncResp->res.jsonValue["RelatedItem@odata.count"] =
885             relatedItem.size();
886     }
887     else
888     {
889         BMCWEB_LOG_ERROR("Unknown software purpose {}", purpose);
890     }
891 }
892 
893 inline void
894     getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
895                        const std::string& service, const std::string& path,
896                        const std::string& swId)
897 {
898     sdbusplus::asio::getAllProperties(
899         *crow::connections::systemBus, service, path,
900         "xyz.openbmc_project.Software.Version",
901         [asyncResp,
902          swId](const boost::system::error_code& ec,
903                const dbus::utility::DBusPropertiesMap& propertiesList) {
904         if (ec)
905         {
906             messages::internalError(asyncResp->res);
907             return;
908         }
909 
910         const std::string* swInvPurpose = nullptr;
911         const std::string* version = nullptr;
912 
913         const bool success = sdbusplus::unpackPropertiesNoThrow(
914             dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose",
915             swInvPurpose, "Version", version);
916 
917         if (!success)
918         {
919             messages::internalError(asyncResp->res);
920             return;
921         }
922 
923         if (swInvPurpose == nullptr)
924         {
925             BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!");
926             messages::internalError(asyncResp->res);
927             return;
928         }
929 
930         BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose);
931 
932         if (version == nullptr)
933         {
934             BMCWEB_LOG_DEBUG("Can't find property \"Version\"!");
935 
936             messages::internalError(asyncResp->res);
937 
938             return;
939         }
940         asyncResp->res.jsonValue["Version"] = *version;
941         asyncResp->res.jsonValue["Id"] = swId;
942 
943         // swInvPurpose is of format:
944         // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
945         // Translate this to "ABC image"
946         size_t endDesc = swInvPurpose->rfind('.');
947         if (endDesc == std::string::npos)
948         {
949             messages::internalError(asyncResp->res);
950             return;
951         }
952         endDesc++;
953         if (endDesc >= swInvPurpose->size())
954         {
955             messages::internalError(asyncResp->res);
956             return;
957         }
958 
959         std::string formatDesc = swInvPurpose->substr(endDesc);
960         asyncResp->res.jsonValue["Description"] = formatDesc + " image";
961         getRelatedItems(asyncResp, *swInvPurpose);
962         });
963 }
964 
965 inline void requestRoutesSoftwareInventory(App& app)
966 {
967     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
968         .privileges(redfish::privileges::getSoftwareInventory)
969         .methods(boost::beast::http::verb::get)(
970             [&app](const crow::Request& req,
971                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
972                    const std::string& param) {
973         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
974         {
975             return;
976         }
977         std::shared_ptr<std::string> swId =
978             std::make_shared<std::string>(param);
979 
980         asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
981             "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId);
982 
983         constexpr std::array<std::string_view, 1> interfaces = {
984             "xyz.openbmc_project.Software.Version"};
985         dbus::utility::getSubTree(
986             "/", 0, interfaces,
987             [asyncResp,
988              swId](const boost::system::error_code& ec,
989                    const dbus::utility::MapperGetSubTreeResponse& subtree) {
990             BMCWEB_LOG_DEBUG("doGet callback...");
991             if (ec)
992             {
993                 messages::internalError(asyncResp->res);
994                 return;
995             }
996 
997             // Ensure we find our input swId, otherwise return an error
998             bool found = false;
999             for (const std::pair<std::string,
1000                                  std::vector<std::pair<
1001                                      std::string, std::vector<std::string>>>>&
1002                      obj : subtree)
1003             {
1004                 if (!obj.first.ends_with(*swId))
1005                 {
1006                     continue;
1007                 }
1008 
1009                 if (obj.second.empty())
1010                 {
1011                     continue;
1012                 }
1013 
1014                 found = true;
1015                 sw_util::getSwStatus(asyncResp, swId, obj.second[0].first);
1016                 getSoftwareVersion(asyncResp, obj.second[0].first, obj.first,
1017                                    *swId);
1018             }
1019             if (!found)
1020             {
1021                 BMCWEB_LOG_WARNING("Input swID {} not found!", *swId);
1022                 messages::resourceMissingAtURI(
1023                     asyncResp->res,
1024                     boost::urls::format(
1025                         "/redfish/v1/UpdateService/FirmwareInventory/{}",
1026                         *swId));
1027                 return;
1028             }
1029             asyncResp->res.jsonValue["@odata.type"] =
1030                 "#SoftwareInventory.v1_1_0.SoftwareInventory";
1031             asyncResp->res.jsonValue["Name"] = "Software Inventory";
1032             asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
1033 
1034             asyncResp->res.jsonValue["Updateable"] = false;
1035             sw_util::getSwUpdatableStatus(asyncResp, swId);
1036             });
1037         });
1038 }
1039 
1040 } // namespace redfish
1041