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