xref: /openbmc/bmcweb/features/redfish/lib/update_service.hpp (revision 1476687deb1697d865b20458a0097c9ab5fd44e2)
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 <query.hpp>
23 #include <registries/privilege_registry.hpp>
24 #include <sdbusplus/asio/property.hpp>
25 #include <utils/fw_utils.hpp>
26 
27 namespace redfish
28 {
29 
30 // Match signals added on software path
31 static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateMatcher;
32 static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateErrorMatcher;
33 // Only allow one update at a time
34 static bool fwUpdateInProgress = false;
35 // Timer for software available
36 static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer;
37 
38 inline static void cleanUp()
39 {
40     fwUpdateInProgress = false;
41     fwUpdateMatcher = nullptr;
42     fwUpdateErrorMatcher = nullptr;
43 }
44 inline static void activateImage(const std::string& objPath,
45                                  const std::string& service)
46 {
47     BMCWEB_LOG_DEBUG << "Activate image for " << objPath << " " << service;
48     crow::connections::systemBus->async_method_call(
49         [](const boost::system::error_code errorCode) {
50             if (errorCode)
51             {
52                 BMCWEB_LOG_DEBUG << "error_code = " << errorCode;
53                 BMCWEB_LOG_DEBUG << "error msg = " << errorCode.message();
54             }
55         },
56         service, objPath, "org.freedesktop.DBus.Properties", "Set",
57         "xyz.openbmc_project.Software.Activation", "RequestedActivation",
58         dbus::utility::DbusVariantType(
59             "xyz.openbmc_project.Software.Activation.RequestedActivations.Active"));
60 }
61 
62 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
63 // then no asyncResp updates will occur
64 static void
65     softwareInterfaceAdded(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
66                            sdbusplus::message::message& m,
67                            task::Payload&& payload)
68 {
69     dbus::utility::DBusInteracesMap interfacesProperties;
70 
71     sdbusplus::message::object_path objPath;
72 
73     m.read(objPath, interfacesProperties);
74 
75     BMCWEB_LOG_DEBUG << "obj path = " << objPath.str;
76     for (auto& interface : interfacesProperties)
77     {
78         BMCWEB_LOG_DEBUG << "interface = " << interface.first;
79 
80         if (interface.first == "xyz.openbmc_project.Software.Activation")
81         {
82             // Retrieve service and activate
83             crow::connections::systemBus->async_method_call(
84                 [objPath, asyncResp, payload(std::move(payload))](
85                     const boost::system::error_code errorCode,
86                     const std::vector<
87                         std::pair<std::string, std::vector<std::string>>>&
88                         objInfo) mutable {
89                     if (errorCode)
90                     {
91                         BMCWEB_LOG_DEBUG << "error_code = " << errorCode;
92                         BMCWEB_LOG_DEBUG << "error msg = "
93                                          << errorCode.message();
94                         if (asyncResp)
95                         {
96                             messages::internalError(asyncResp->res);
97                         }
98                         cleanUp();
99                         return;
100                     }
101                     // Ensure we only got one service back
102                     if (objInfo.size() != 1)
103                     {
104                         BMCWEB_LOG_ERROR << "Invalid Object Size "
105                                          << objInfo.size();
106                         if (asyncResp)
107                         {
108                             messages::internalError(asyncResp->res);
109                         }
110                         cleanUp();
111                         return;
112                     }
113                     // cancel timer only when
114                     // xyz.openbmc_project.Software.Activation interface
115                     // is added
116                     fwAvailableTimer = nullptr;
117 
118                     activateImage(objPath.str, objInfo[0].first);
119                     if (asyncResp)
120                     {
121                         std::shared_ptr<task::TaskData> task =
122                             task::TaskData::createTask(
123                                 [](boost::system::error_code ec,
124                                    sdbusplus::message::message& msg,
125                                    const std::shared_ptr<task::TaskData>&
126                                        taskData) {
127                                     if (ec)
128                                     {
129                                         return task::completed;
130                                     }
131 
132                                     std::string iface;
133                                     dbus::utility::DBusPropertiesMap values;
134 
135                                     std::string index =
136                                         std::to_string(taskData->index);
137                                     msg.read(iface, values);
138 
139                                     if (iface ==
140                                         "xyz.openbmc_project.Software.Activation")
141                                     {
142                                         std::string* state = nullptr;
143                                         for (const auto& property : values)
144                                         {
145                                             if (property.first == "Activation")
146                                             {
147                                                 const std::string* state =
148                                                     std::get_if<std::string>(
149                                                         &property.second);
150                                                 if (state == nullptr)
151                                                 {
152                                                     taskData->messages
153                                                         .emplace_back(
154                                                             messages::
155                                                                 internalError());
156                                                     return task::completed;
157                                                 }
158                                             }
159                                         }
160 
161                                         if (state == nullptr)
162                                         {
163                                             return !task::completed;
164                                         }
165 
166                                         if (boost::ends_with(*state,
167                                                              "Invalid") ||
168                                             boost::ends_with(*state, "Failed"))
169                                         {
170                                             taskData->state = "Exception";
171                                             taskData->status = "Warning";
172                                             taskData->messages.emplace_back(
173                                                 messages::taskAborted(index));
174                                             return task::completed;
175                                         }
176 
177                                         if (boost::ends_with(*state, "Staged"))
178                                         {
179                                             taskData->state = "Stopping";
180                                             taskData->messages.emplace_back(
181                                                 messages::taskPaused(index));
182 
183                                             // its staged, set a long timer to
184                                             // allow them time to complete the
185                                             // update (probably cycle the
186                                             // system) if this expires then
187                                             // task will be cancelled
188                                             taskData->extendTimer(
189                                                 std::chrono::hours(5));
190                                             return !task::completed;
191                                         }
192 
193                                         if (boost::ends_with(*state, "Active"))
194                                         {
195                                             taskData->messages.emplace_back(
196                                                 messages::taskCompletedOK(
197                                                     index));
198                                             taskData->state = "Completed";
199                                             return task::completed;
200                                         }
201                                     }
202                                     else if (
203                                         iface ==
204                                         "xyz.openbmc_project.Software.ActivationProgress")
205                                     {
206 
207                                         const uint8_t* progress = nullptr;
208                                         for (const auto& property : values)
209                                         {
210                                             if (property.first == "Progress")
211                                             {
212                                                 const std::string* progress =
213                                                     std::get_if<std::string>(
214                                                         &property.second);
215                                                 if (progress == nullptr)
216                                                 {
217                                                     taskData->messages
218                                                         .emplace_back(
219                                                             messages::
220                                                                 internalError());
221                                                     return task::completed;
222                                                 }
223                                             }
224                                         }
225 
226                                         if (progress == nullptr)
227                                         {
228                                             return !task::completed;
229                                         }
230                                         taskData->percentComplete =
231                                             static_cast<int>(*progress);
232                                         taskData->messages.emplace_back(
233                                             messages::taskProgressChanged(
234                                                 index, static_cast<size_t>(
235                                                            *progress)));
236 
237                                         // if we're getting status updates it's
238                                         // still alive, update timer
239                                         taskData->extendTimer(
240                                             std::chrono::minutes(5));
241                                     }
242 
243                                     // as firmware update often results in a
244                                     // reboot, the task  may never "complete"
245                                     // unless it is an error
246 
247                                     return !task::completed;
248                                 },
249                                 "type='signal',interface='org.freedesktop.DBus.Properties',"
250                                 "member='PropertiesChanged',path='" +
251                                     objPath.str + "'");
252                         task->startTimer(std::chrono::minutes(5));
253                         task->populateResp(asyncResp->res);
254                         task->payload.emplace(std::move(payload));
255                     }
256                     fwUpdateInProgress = false;
257                 },
258                 "xyz.openbmc_project.ObjectMapper",
259                 "/xyz/openbmc_project/object_mapper",
260                 "xyz.openbmc_project.ObjectMapper", "GetObject", objPath.str,
261                 std::array<const char*, 1>{
262                     "xyz.openbmc_project.Software.Activation"});
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 = 10)
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
298                 << "Timed out waiting for firmware object being created";
299             BMCWEB_LOG_ERROR
300                 << "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,
313                      payload](sdbusplus::message::message& m) mutable {
314         BMCWEB_LOG_DEBUG << "Match fired";
315         softwareInterfaceAdded(asyncResp, m, std::move(payload));
316     };
317 
318     fwUpdateInProgress = true;
319 
320     fwUpdateMatcher = std::make_unique<sdbusplus::bus::match::match>(
321         *crow::connections::systemBus,
322         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
323         "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
324         callback);
325 
326     fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match::match>(
327         *crow::connections::systemBus,
328         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
329         "member='InterfacesAdded',"
330         "path='/xyz/openbmc_project/logging'",
331         [asyncResp, url](sdbusplus::message::message& m) {
332             std::vector<
333                 std::pair<std::string, dbus::utility::DBusPropertiesMap>>
334                 interfacesProperties;
335             sdbusplus::message::object_path objPath;
336             m.read(objPath, interfacesProperties);
337             BMCWEB_LOG_DEBUG << "obj path = " << objPath.str;
338             for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>&
339                      interface : interfacesProperties)
340             {
341                 if (interface.first == "xyz.openbmc_project.Logging.Entry")
342                 {
343                     for (const std::pair<std::string,
344                                          dbus::utility::DbusVariantType>&
345                              value : interface.second)
346                     {
347                         if (value.first != "Message")
348                         {
349                             continue;
350                         }
351                         const std::string* type =
352                             std::get_if<std::string>(&value.second);
353                         if (type == nullptr)
354                         {
355                             // if this was our message, timeout will cover it
356                             return;
357                         }
358                         fwAvailableTimer = nullptr;
359                         if (*type ==
360                             "xyz.openbmc_project.Software.Image.Error.UnTarFailure")
361                         {
362                             redfish::messages::invalidUpload(
363                                 asyncResp->res, url, "Invalid archive");
364                         }
365                         else if (*type ==
366                                  "xyz.openbmc_project.Software.Image.Error."
367                                  "ManifestFileFailure")
368                         {
369                             redfish::messages::invalidUpload(
370                                 asyncResp->res, url, "Invalid manifest");
371                         }
372                         else if (
373                             *type ==
374                             "xyz.openbmc_project.Software.Image.Error.ImageFailure")
375                         {
376                             redfish::messages::invalidUpload(
377                                 asyncResp->res, url, "Invalid image format");
378                         }
379                         else if (
380                             *type ==
381                             "xyz.openbmc_project.Software.Version.Error.AlreadyExists")
382                         {
383                             redfish::messages::invalidUpload(
384                                 asyncResp->res, url,
385                                 "Image version already exists");
386 
387                             redfish::messages::resourceAlreadyExists(
388                                 asyncResp->res,
389                                 "UpdateService.v1_5_0.UpdateService", "Version",
390                                 "uploaded version");
391                         }
392                         else if (
393                             *type ==
394                             "xyz.openbmc_project.Software.Image.Error.BusyFailure")
395                         {
396                             redfish::messages::resourceExhaustion(
397                                 asyncResp->res, url);
398                         }
399                         else
400                         {
401                             redfish::messages::internalError(asyncResp->res);
402                         }
403                     }
404                 }
405             }
406         });
407 }
408 
409 /**
410  * UpdateServiceActionsSimpleUpdate class supports handle POST method for
411  * SimpleUpdate action.
412  */
413 inline void requestRoutesUpdateServiceActionsSimpleUpdate(App& app)
414 {
415     BMCWEB_ROUTE(
416         app, "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/")
417         .privileges(redfish::privileges::postUpdateService)
418         .methods(
419             boost::beast::http::verb::
420                 post)([&app](
421                           const crow::Request& req,
422                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
423             if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
424             {
425                 return;
426             }
427 
428             std::optional<std::string> transferProtocol;
429             std::string imageURI;
430 
431             BMCWEB_LOG_DEBUG << "Enter UpdateService.SimpleUpdate doPost";
432 
433             // User can pass in both TransferProtocol and ImageURI parameters or
434             // they can pass in just the ImageURI with the transfer protocol
435             // embedded within it.
436             // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin
437             // 2) ImageURI:tftp://1.1.1.1/myfile.bin
438 
439             if (!json_util::readJsonAction(req, asyncResp->res,
440                                            "TransferProtocol", transferProtocol,
441                                            "ImageURI", imageURI))
442             {
443                 BMCWEB_LOG_DEBUG
444                     << "Missing TransferProtocol or ImageURI parameter";
445                 return;
446             }
447             if (!transferProtocol)
448             {
449                 // Must be option 2
450                 // Verify ImageURI has transfer protocol in it
451                 size_t separator = imageURI.find(':');
452                 if ((separator == std::string::npos) ||
453                     ((separator + 1) > imageURI.size()))
454                 {
455                     messages::actionParameterValueTypeError(
456                         asyncResp->res, imageURI, "ImageURI",
457                         "UpdateService.SimpleUpdate");
458                     BMCWEB_LOG_ERROR << "ImageURI missing transfer protocol: "
459                                      << imageURI;
460                     return;
461                 }
462                 transferProtocol = imageURI.substr(0, separator);
463                 // Ensure protocol is upper case for a common comparison path
464                 // below
465                 boost::to_upper(*transferProtocol);
466                 BMCWEB_LOG_DEBUG << "Encoded transfer protocol "
467                                  << *transferProtocol;
468 
469                 // Adjust imageURI to not have the protocol on it for parsing
470                 // below
471                 // ex. tftp://1.1.1.1/myfile.bin -> 1.1.1.1/myfile.bin
472                 imageURI = imageURI.substr(separator + 3);
473                 BMCWEB_LOG_DEBUG << "Adjusted imageUri " << imageURI;
474             }
475 
476             // OpenBMC currently only supports TFTP
477             if (*transferProtocol != "TFTP")
478             {
479                 messages::actionParameterNotSupported(
480                     asyncResp->res, "TransferProtocol",
481                     "UpdateService.SimpleUpdate");
482                 BMCWEB_LOG_ERROR << "Request incorrect protocol parameter: "
483                                  << *transferProtocol;
484                 return;
485             }
486 
487             // Format should be <IP or Hostname>/<file> for imageURI
488             size_t separator = imageURI.find('/');
489             if ((separator == std::string::npos) ||
490                 ((separator + 1) > imageURI.size()))
491             {
492                 messages::actionParameterValueTypeError(
493                     asyncResp->res, imageURI, "ImageURI",
494                     "UpdateService.SimpleUpdate");
495                 BMCWEB_LOG_ERROR << "Invalid ImageURI: " << imageURI;
496                 return;
497             }
498 
499             std::string tftpServer = imageURI.substr(0, separator);
500             std::string fwFile = imageURI.substr(separator + 1);
501             BMCWEB_LOG_DEBUG << "Server: " << tftpServer + " File: " << fwFile;
502 
503             // Setup callback for when new software detected
504             // Give TFTP 10 minutes to complete
505             monitorForSoftwareAvailable(
506                 asyncResp, req,
507                 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate",
508                 600);
509 
510             // TFTP can take up to 10 minutes depending on image size and
511             // connection speed. Return to caller as soon as the TFTP operation
512             // has been started. The callback above will ensure the activate
513             // is started once the download has completed
514             redfish::messages::success(asyncResp->res);
515 
516             // Call TFTP service
517             crow::connections::systemBus->async_method_call(
518                 [](const boost::system::error_code ec) {
519                     if (ec)
520                     {
521                         // messages::internalError(asyncResp->res);
522                         cleanUp();
523                         BMCWEB_LOG_DEBUG << "error_code = " << ec;
524                         BMCWEB_LOG_DEBUG << "error msg = " << ec.message();
525                     }
526                     else
527                     {
528                         BMCWEB_LOG_DEBUG << "Call to DownloaViaTFTP Success";
529                     }
530                 },
531                 "xyz.openbmc_project.Software.Download",
532                 "/xyz/openbmc_project/software",
533                 "xyz.openbmc_project.Common.TFTP", "DownloadViaTFTP", fwFile,
534                 tftpServer);
535 
536             BMCWEB_LOG_DEBUG << "Exit UpdateService.SimpleUpdate doPost";
537         });
538 }
539 
540 inline void requestRoutesUpdateService(App& app)
541 {
542     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
543         .privileges(redfish::privileges::getUpdateService)
544         .methods(boost::beast::http::verb::get)([&app](const crow::Request& req,
545                                                        const std::shared_ptr<
546                                                            bmcweb::AsyncResp>&
547                                                            asyncResp) {
548             if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
549             {
550                 return;
551             }
552             asyncResp->res.jsonValue["@odata.type"] =
553                 "#UpdateService.v1_5_0.UpdateService";
554             asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
555             asyncResp->res.jsonValue["Id"] = "UpdateService";
556             asyncResp->res.jsonValue["Description"] =
557                 "Service for Software Update";
558             asyncResp->res.jsonValue["Name"] = "Update Service";
559             asyncResp->res.jsonValue["HttpPushUri"] =
560                 "/redfish/v1/UpdateService";
561             // UpdateService cannot be disabled
562             asyncResp->res.jsonValue["ServiceEnabled"] = true;
563             asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] =
564                 "/redfish/v1/UpdateService/FirmwareInventory";
565             // Get the MaxImageSizeBytes
566             asyncResp->res.jsonValue["MaxImageSizeBytes"] =
567                 bmcwebHttpReqBodyLimitMb * 1024 * 1024;
568 
569 #ifdef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE
570             // Update Actions object.
571             nlohmann::json& updateSvcSimpleUpdate =
572                 asyncResp->res
573                     .jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
574             updateSvcSimpleUpdate["target"] =
575                 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
576             updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] =
577                 {"TFTP"};
578 #endif
579             // Get the current ApplyTime value
580             sdbusplus::asio::getProperty<std::string>(
581                 *crow::connections::systemBus, "xyz.openbmc_project.Settings",
582                 "/xyz/openbmc_project/software/apply_time",
583                 "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime",
584                 [asyncResp](const boost::system::error_code ec,
585                             const std::string& applyTime) {
586                     if (ec)
587                     {
588                         BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
589                         messages::internalError(asyncResp->res);
590                         return;
591                     }
592 
593                     // Store the ApplyTime Value
594                     if (applyTime == "xyz.openbmc_project.Software.ApplyTime."
595                                      "RequestedApplyTimes.Immediate")
596                     {
597                         asyncResp->res
598                             .jsonValue["HttpPushUriOptions"]
599                                       ["HttpPushUriApplyTime"]["ApplyTime"] =
600                             "Immediate";
601                     }
602                     else if (applyTime ==
603                              "xyz.openbmc_project.Software.ApplyTime."
604                              "RequestedApplyTimes.OnReset")
605                     {
606                         asyncResp->res
607                             .jsonValue["HttpPushUriOptions"]
608                                       ["HttpPushUriApplyTime"]["ApplyTime"] =
609                             "OnReset";
610                     }
611                 });
612         });
613     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
614         .privileges(redfish::privileges::patchUpdateService)
615         .methods(
616             boost::beast::http::verb::patch)([&app](const crow::Request& req,
617                                                     const std::shared_ptr<
618                                                         bmcweb::AsyncResp>&
619                                                         asyncResp) {
620             if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
621             {
622                 return;
623             }
624             BMCWEB_LOG_DEBUG << "doPatch...";
625 
626             std::optional<nlohmann::json> pushUriOptions;
627             if (!json_util::readJsonPatch(req, asyncResp->res,
628                                           "HttpPushUriOptions", pushUriOptions))
629             {
630                 return;
631             }
632 
633             if (pushUriOptions)
634             {
635                 std::optional<nlohmann::json> pushUriApplyTime;
636                 if (!json_util::readJson(*pushUriOptions, asyncResp->res,
637                                          "HttpPushUriApplyTime",
638                                          pushUriApplyTime))
639                 {
640                     return;
641                 }
642 
643                 if (pushUriApplyTime)
644                 {
645                     std::optional<std::string> applyTime;
646                     if (!json_util::readJson(*pushUriApplyTime, asyncResp->res,
647                                              "ApplyTime", applyTime))
648                     {
649                         return;
650                     }
651 
652                     if (applyTime)
653                     {
654                         std::string applyTimeNewVal;
655                         if (applyTime == "Immediate")
656                         {
657                             applyTimeNewVal =
658                                 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
659                         }
660                         else if (applyTime == "OnReset")
661                         {
662                             applyTimeNewVal =
663                                 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
664                         }
665                         else
666                         {
667                             BMCWEB_LOG_INFO
668                                 << "ApplyTime value is not in the list of acceptable values";
669                             messages::propertyValueNotInList(
670                                 asyncResp->res, *applyTime, "ApplyTime");
671                             return;
672                         }
673 
674                         // Set the requested image apply time value
675                         crow::connections::systemBus->async_method_call(
676                             [asyncResp](const boost::system::error_code ec) {
677                                 if (ec)
678                                 {
679                                     BMCWEB_LOG_ERROR
680                                         << "D-Bus responses error: " << ec;
681                                     messages::internalError(asyncResp->res);
682                                     return;
683                                 }
684                                 messages::success(asyncResp->res);
685                             },
686                             "xyz.openbmc_project.Settings",
687                             "/xyz/openbmc_project/software/apply_time",
688                             "org.freedesktop.DBus.Properties", "Set",
689                             "xyz.openbmc_project.Software.ApplyTime",
690                             "RequestedApplyTime",
691                             dbus::utility::DbusVariantType{applyTimeNewVal});
692                     }
693                 }
694             }
695         });
696     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
697         .privileges(redfish::privileges::postUpdateService)
698         .methods(boost::beast::http::verb::post)(
699             [&app](const crow::Request& req,
700                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
701                 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
702                 {
703                     return;
704                 }
705                 BMCWEB_LOG_DEBUG << "doPost...";
706 
707                 // Setup callback for when new software detected
708                 monitorForSoftwareAvailable(asyncResp, req,
709                                             "/redfish/v1/UpdateService");
710 
711                 std::string filepath("/tmp/images/" +
712                                      boost::uuids::to_string(
713                                          boost::uuids::random_generator()()));
714                 BMCWEB_LOG_DEBUG << "Writing file to " << filepath;
715                 std::ofstream out(filepath, std::ofstream::out |
716                                                 std::ofstream::binary |
717                                                 std::ofstream::trunc);
718                 out << req.body;
719                 out.close();
720                 BMCWEB_LOG_DEBUG << "file upload complete!!";
721             });
722 }
723 
724 inline void requestRoutesSoftwareInventoryCollection(App& app)
725 {
726     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/")
727         .privileges(redfish::privileges::getSoftwareInventoryCollection)
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->res))
732                 {
733                     return;
734                 }
735                 asyncResp->res.jsonValue["@odata.type"] =
736                     "#SoftwareInventoryCollection.SoftwareInventoryCollection";
737                 asyncResp->res.jsonValue["@odata.id"] =
738                     "/redfish/v1/UpdateService/FirmwareInventory";
739                 asyncResp->res.jsonValue["Name"] =
740                     "Software Inventory Collection";
741 
742                 crow::connections::systemBus->async_method_call(
743                     [asyncResp](const boost::system::error_code ec,
744                                 const dbus::utility::MapperGetSubTreeResponse&
745                                     subtree) {
746                         if (ec)
747                         {
748                             messages::internalError(asyncResp->res);
749                             return;
750                         }
751                         asyncResp->res.jsonValue["Members"] =
752                             nlohmann::json::array();
753                         asyncResp->res.jsonValue["Members@odata.count"] = 0;
754 
755                         for (const auto& obj : subtree)
756                         {
757                             sdbusplus::message::object_path path(obj.first);
758                             std::string swId = path.filename();
759                             if (swId.empty())
760                             {
761                                 messages::internalError(asyncResp->res);
762                                 BMCWEB_LOG_DEBUG << "Can't parse firmware ID!!";
763                                 return;
764                             }
765 
766                             nlohmann::json& members =
767                                 asyncResp->res.jsonValue["Members"];
768                             nlohmann::json::object_t member;
769                             member["@odata.id"] =
770                                 "/redfish/v1/UpdateService/FirmwareInventory/" +
771                                 swId;
772                             members.push_back(std::move(member));
773                             asyncResp->res.jsonValue["Members@odata.count"] =
774                                 members.size();
775                         }
776                     },
777                     // Note that only firmware levels associated with a device
778                     // are stored under /xyz/openbmc_project/software therefore
779                     // to ensure only real FirmwareInventory items are returned,
780                     // this full object path must be used here as input to
781                     // mapper
782                     "xyz.openbmc_project.ObjectMapper",
783                     "/xyz/openbmc_project/object_mapper",
784                     "xyz.openbmc_project.ObjectMapper", "GetSubTree",
785                     "/xyz/openbmc_project/software", static_cast<int32_t>(0),
786                     std::array<const char*, 1>{
787                         "xyz.openbmc_project.Software.Version"});
788             });
789 }
790 /* Fill related item links (i.e. bmc, bios) in for inventory */
791 inline static void
792     getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
793                     const std::string& purpose)
794 {
795     if (purpose == fw_util::bmcPurpose)
796     {
797         nlohmann::json& relatedItem = aResp->res.jsonValue["RelatedItem"];
798         nlohmann::json::object_t item;
799         item["@odata.id"] = "/redfish/v1/Managers/bmc";
800         relatedItem.push_back(std::move(item));
801         aResp->res.jsonValue["RelatedItem@odata.count"] = relatedItem.size();
802     }
803     else if (purpose == fw_util::biosPurpose)
804     {
805         nlohmann::json& relatedItem = aResp->res.jsonValue["RelatedItem"];
806         nlohmann::json::object_t item;
807         item["@odata.id"] = "/redfish/v1/Systems/system/Bios";
808         relatedItem.push_back(std::move(item));
809         aResp->res.jsonValue["RelatedItem@odata.count"] = relatedItem.size();
810     }
811     else
812     {
813         BMCWEB_LOG_ERROR << "Unknown software purpose " << purpose;
814     }
815 }
816 
817 inline void requestRoutesSoftwareInventory(App& app)
818 {
819     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
820         .privileges(redfish::privileges::getSoftwareInventory)
821         .methods(
822             boost::beast::http::verb::
823                 get)([&app](const crow::Request& req,
824                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
825                             const std::string& param) {
826             if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
827             {
828                 return;
829             }
830             std::shared_ptr<std::string> swId =
831                 std::make_shared<std::string>(param);
832 
833             asyncResp->res.jsonValue["@odata.id"] =
834                 "/redfish/v1/UpdateService/FirmwareInventory/" + *swId;
835 
836             crow::connections::systemBus->async_method_call(
837                 [asyncResp,
838                  swId](const boost::system::error_code ec,
839                        const dbus::utility::MapperGetSubTreeResponse& subtree) {
840                     BMCWEB_LOG_DEBUG << "doGet callback...";
841                     if (ec)
842                     {
843                         messages::internalError(asyncResp->res);
844                         return;
845                     }
846 
847                     // Ensure we find our input swId, otherwise return an error
848                     bool found = false;
849                     for (const std::pair<
850                              std::string,
851                              std::vector<std::pair<
852                                  std::string, std::vector<std::string>>>>& obj :
853                          subtree)
854                     {
855                         if (!boost::ends_with(obj.first, *swId))
856                         {
857                             continue;
858                         }
859 
860                         if (obj.second.empty())
861                         {
862                             continue;
863                         }
864 
865                         found = true;
866                         fw_util::getFwStatus(asyncResp, swId,
867                                              obj.second[0].first);
868 
869                         crow::connections::systemBus->async_method_call(
870                             [asyncResp,
871                              swId](const boost::system::error_code errorCode,
872                                    const dbus::utility::DBusPropertiesMap&
873                                        propertiesList) {
874                                 if (errorCode)
875                                 {
876                                     messages::internalError(asyncResp->res);
877                                     return;
878                                 }
879                                 const std::string* swInvPurpose = nullptr;
880                                 const std::string* version = nullptr;
881                                 for (const auto& property : propertiesList)
882                                 {
883                                     if (property.first == "Purpose")
884                                     {
885                                         swInvPurpose = std::get_if<std::string>(
886                                             &property.second);
887                                     }
888                                     if (property.first == "Version")
889                                     {
890                                         version = std::get_if<std::string>(
891                                             &property.second);
892                                     }
893                                 }
894 
895                                 if (swInvPurpose == nullptr)
896                                 {
897                                     BMCWEB_LOG_DEBUG
898                                         << "Can't find property \"Purpose\"!";
899                                     messages::internalError(asyncResp->res);
900                                     return;
901                                 }
902 
903                                 BMCWEB_LOG_DEBUG << "swInvPurpose = "
904                                                  << *swInvPurpose;
905 
906                                 if (version == nullptr)
907                                 {
908                                     BMCWEB_LOG_DEBUG
909                                         << "Can't find property \"Version\"!";
910 
911                                     messages::internalError(asyncResp->res);
912 
913                                     return;
914                                 }
915                                 asyncResp->res.jsonValue["Version"] = *version;
916                                 asyncResp->res.jsonValue["Id"] = *swId;
917 
918                                 // swInvPurpose is of format:
919                                 // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
920                                 // Translate this to "ABC image"
921                                 size_t endDesc = swInvPurpose->rfind('.');
922                                 if (endDesc == std::string::npos)
923                                 {
924                                     messages::internalError(asyncResp->res);
925                                     return;
926                                 }
927                                 endDesc++;
928                                 if (endDesc >= swInvPurpose->size())
929                                 {
930                                     messages::internalError(asyncResp->res);
931                                     return;
932                                 }
933 
934                                 std::string formatDesc =
935                                     swInvPurpose->substr(endDesc);
936                                 asyncResp->res.jsonValue["Description"] =
937                                     formatDesc + " image";
938                                 getRelatedItems(asyncResp, *swInvPurpose);
939                             },
940                             obj.second[0].first, obj.first,
941                             "org.freedesktop.DBus.Properties", "GetAll",
942                             "xyz.openbmc_project.Software.Version");
943                     }
944                     if (!found)
945                     {
946                         BMCWEB_LOG_ERROR << "Input swID " << *swId
947                                          << " not found!";
948                         messages::resourceMissingAtURI(
949                             asyncResp->res,
950                             crow::utility::urlFromPieces(
951                                 "redfish", "v1", "UpdateService",
952                                 "FirmwareInventory", *swId));
953                         return;
954                     }
955                     asyncResp->res.jsonValue["@odata.type"] =
956                         "#SoftwareInventory.v1_1_0.SoftwareInventory";
957                     asyncResp->res.jsonValue["Name"] = "Software Inventory";
958                     asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
959 
960                     asyncResp->res.jsonValue["Updateable"] = false;
961                     fw_util::getFwUpdateableStatus(asyncResp, swId);
962                 },
963                 "xyz.openbmc_project.ObjectMapper",
964                 "/xyz/openbmc_project/object_mapper",
965                 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/",
966                 static_cast<int32_t>(0),
967                 std::array<const char*, 1>{
968                     "xyz.openbmc_project.Software.Version"});
969         });
970 }
971 
972 } // namespace redfish
973