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