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