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