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