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