xref: /openbmc/bmcweb/features/redfish/lib/update_service.hpp (revision 3a17e02873fdf53898a2cfce894dcb36223d76d4)
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 
23 #include <variant>
24 
25 namespace redfish
26 {
27 
28 // Match signals added on software path
29 static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateMatcher;
30 static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateErrorMatcher;
31 // Only allow one update at a time
32 static bool fwUpdateInProgress = false;
33 // Timer for software available
34 static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer;
35 
36 static void cleanUp()
37 {
38     fwUpdateInProgress = false;
39     fwUpdateMatcher = nullptr;
40     fwUpdateErrorMatcher = nullptr;
41 }
42 static void activateImage(const std::string& objPath,
43                           const std::string& service)
44 {
45     BMCWEB_LOG_DEBUG << "Activate image for " << objPath << " " << service;
46     crow::connections::systemBus->async_method_call(
47         [](const boost::system::error_code error_code) {
48             if (error_code)
49             {
50                 BMCWEB_LOG_DEBUG << "error_code = " << error_code;
51                 BMCWEB_LOG_DEBUG << "error msg = " << error_code.message();
52             }
53         },
54         service, objPath, "org.freedesktop.DBus.Properties", "Set",
55         "xyz.openbmc_project.Software.Activation", "RequestedActivation",
56         std::variant<std::string>(
57             "xyz.openbmc_project.Software.Activation.RequestedActivations."
58             "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 softwareInterfaceAdded(const std::shared_ptr<AsyncResp>& asyncResp,
64                                    sdbusplus::message::message& m,
65                                    const crow::Request& req)
66 {
67     std::vector<std::pair<
68         std::string,
69         std::vector<std::pair<std::string, std::variant<std::string>>>>>
70         interfacesProperties;
71 
72     sdbusplus::message::object_path objPath;
73 
74     m.read(objPath, interfacesProperties);
75 
76     BMCWEB_LOG_DEBUG << "obj path = " << objPath.str;
77     for (auto& interface : interfacesProperties)
78     {
79         BMCWEB_LOG_DEBUG << "interface = " << interface.first;
80 
81         if (interface.first == "xyz.openbmc_project.Software.Activation")
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(
257     const std::shared_ptr<AsyncResp>& asyncResp, const crow::Request& req,
258     const std::string& url, int timeoutTimeSeconds = 10)
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     fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match::match>(
312         *crow::connections::systemBus,
313         "type='signal',member='PropertiesChanged',path_namespace='/xyz/"
314         "openbmc_project/logging/entry',"
315         "arg0='xyz.openbmc_project.Logging.Entry'",
316         [asyncResp, url](sdbusplus::message::message& m) {
317             BMCWEB_LOG_DEBUG << "Error Match fired";
318             boost::container::flat_map<std::string, std::variant<std::string>>
319                 values;
320             std::string objName;
321             m.read(objName, values);
322             auto find = values.find("Message");
323             if (find == values.end())
324             {
325                 return;
326             }
327             std::string* type = std::get_if<std::string>(&(find->second));
328             if (type == nullptr)
329             {
330                 return; // if this was our message, timeout will cover it
331             }
332             if (!boost::starts_with(*type,
333                                     "xyz.openbmc_project.Software.Image.Error"))
334             {
335                 return;
336             }
337             if (*type ==
338                 "xyz.openbmc_project.Software.Image.Error.UnTarFailure")
339             {
340                 redfish::messages::invalidUpload(asyncResp->res, url,
341                                                  "Invalid archive");
342             }
343             else if (*type == "xyz.openbmc_project.Software.Image.Error."
344                               "ManifestFileFailure")
345             {
346                 redfish::messages::invalidUpload(asyncResp->res, url,
347                                                  "Invalid manifest");
348             }
349             else if (*type ==
350                      "xyz.openbmc_project.Software.Image.Error.ImageFailure")
351             {
352                 redfish::messages::invalidUpload(asyncResp->res, url,
353                                                  "Invalid image format");
354             }
355             else if (*type ==
356                      "xyz.openbmc_project.Software.Image.Error.BusyFailure")
357             {
358                 redfish::messages::resourceExhaustion(asyncResp->res, url);
359             }
360             else
361             {
362                 redfish::messages::internalError(asyncResp->res);
363             }
364             fwAvailableTimer = nullptr;
365         });
366 }
367 
368 /**
369  * UpdateServiceActionsSimpleUpdate class supports handle POST method for
370  * SimpleUpdate action.
371  */
372 class UpdateServiceActionsSimpleUpdate : public Node
373 {
374   public:
375     UpdateServiceActionsSimpleUpdate(App& app) :
376         Node(app,
377              "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/")
378     {
379         entityPrivileges = {
380             {boost::beast::http::verb::get, {{"Login"}}},
381             {boost::beast::http::verb::head, {{"Login"}}},
382             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
383             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
384             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
385             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
386     }
387 
388   private:
389     void doPost(crow::Response& res, const crow::Request& req,
390                 const std::vector<std::string>&) override
391     {
392         std::optional<std::string> transferProtocol;
393         std::string imageURI;
394         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
395 
396         BMCWEB_LOG_DEBUG << "Enter UpdateService.SimpleUpdate doPost";
397 
398         // User can pass in both TransferProtocol and ImageURI parameters or
399         // they can pass in just the ImageURI with the transfer protocol
400         // embedded within it.
401         // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin
402         // 2) ImageURI:tftp://1.1.1.1/myfile.bin
403 
404         if (!json_util::readJson(req, asyncResp->res, "TransferProtocol",
405                                  transferProtocol, "ImageURI", imageURI))
406         {
407             BMCWEB_LOG_DEBUG
408                 << "Missing TransferProtocol or ImageURI parameter";
409             return;
410         }
411         if (!transferProtocol)
412         {
413             // Must be option 2
414             // Verify ImageURI has transfer protocol in it
415             size_t separator = imageURI.find(':');
416             if ((separator == std::string::npos) ||
417                 ((separator + 1) > imageURI.size()))
418             {
419                 messages::actionParameterValueTypeError(
420                     asyncResp->res, imageURI, "ImageURI",
421                     "UpdateService.SimpleUpdate");
422                 BMCWEB_LOG_ERROR << "ImageURI missing transfer protocol: "
423                                  << imageURI;
424                 return;
425             }
426             transferProtocol = imageURI.substr(0, separator);
427             // Ensure protocol is upper case for a common comparison path below
428             boost::to_upper(*transferProtocol);
429             BMCWEB_LOG_DEBUG << "Encoded transfer protocol "
430                              << *transferProtocol;
431 
432             // Adjust imageURI to not have the protocol on it for parsing
433             // below
434             // ex. tftp://1.1.1.1/myfile.bin -> 1.1.1.1/myfile.bin
435             imageURI = imageURI.substr(separator + 3);
436             BMCWEB_LOG_DEBUG << "Adjusted imageUri " << imageURI;
437         }
438 
439         // OpenBMC currently only supports TFTP
440         if (*transferProtocol != "TFTP")
441         {
442             messages::actionParameterNotSupported(asyncResp->res,
443                                                   "TransferProtocol",
444                                                   "UpdateService.SimpleUpdate");
445             BMCWEB_LOG_ERROR << "Request incorrect protocol parameter: "
446                              << *transferProtocol;
447             return;
448         }
449 
450         // Format should be <IP or Hostname>/<file> for imageURI
451         size_t separator = imageURI.find('/');
452         if ((separator == std::string::npos) ||
453             ((separator + 1) > imageURI.size()))
454         {
455             messages::actionParameterValueTypeError(
456                 asyncResp->res, imageURI, "ImageURI",
457                 "UpdateService.SimpleUpdate");
458             BMCWEB_LOG_ERROR << "Invalid ImageURI: " << imageURI;
459             return;
460         }
461 
462         std::string tftpServer = imageURI.substr(0, separator);
463         std::string fwFile = imageURI.substr(separator + 1);
464         BMCWEB_LOG_DEBUG << "Server: " << tftpServer + " File: " << fwFile;
465 
466         // Setup callback for when new software detected
467         // Give TFTP 10 minutes to complete
468         monitorForSoftwareAvailable(
469             nullptr, req,
470             "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate",
471             600);
472 
473         // TFTP can take up to 10 minutes depending on image size and
474         // connection speed. Return to caller as soon as the TFTP operation
475         // has been started. The callback above will ensure the activate
476         // is started once the download has completed
477         redfish::messages::success(asyncResp->res);
478 
479         // Call TFTP service
480         crow::connections::systemBus->async_method_call(
481             [](const boost::system::error_code ec) {
482                 if (ec)
483                 {
484                     // messages::internalError(asyncResp->res);
485                     cleanUp();
486                     BMCWEB_LOG_DEBUG << "error_code = " << ec;
487                     BMCWEB_LOG_DEBUG << "error msg = " << ec.message();
488                 }
489                 else
490                 {
491                     BMCWEB_LOG_DEBUG << "Call to DownloaViaTFTP Success";
492                 }
493             },
494             "xyz.openbmc_project.Software.Download",
495             "/xyz/openbmc_project/software", "xyz.openbmc_project.Common.TFTP",
496             "DownloadViaTFTP", fwFile, tftpServer);
497 
498         BMCWEB_LOG_DEBUG << "Exit UpdateService.SimpleUpdate doPost";
499     }
500 };
501 
502 class UpdateService : public Node
503 {
504   public:
505     UpdateService(App& app) : Node(app, "/redfish/v1/UpdateService/")
506     {
507         entityPrivileges = {
508             {boost::beast::http::verb::get, {{"Login"}}},
509             {boost::beast::http::verb::head, {{"Login"}}},
510             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
511             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
512             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
513             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
514     }
515 
516   private:
517     void doGet(crow::Response& res, const crow::Request&,
518                const std::vector<std::string>&) override
519     {
520         std::shared_ptr<AsyncResp> aResp = std::make_shared<AsyncResp>(res);
521         res.jsonValue["@odata.type"] = "#UpdateService.v1_4_0.UpdateService";
522         res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
523         res.jsonValue["Id"] = "UpdateService";
524         res.jsonValue["Description"] = "Service for Software Update";
525         res.jsonValue["Name"] = "Update Service";
526         res.jsonValue["HttpPushUri"] = "/redfish/v1/UpdateService";
527         // UpdateService cannot be disabled
528         res.jsonValue["ServiceEnabled"] = true;
529         res.jsonValue["FirmwareInventory"] = {
530             {"@odata.id", "/redfish/v1/UpdateService/FirmwareInventory"}};
531 #ifdef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE
532         // Update Actions object.
533         nlohmann::json& updateSvcSimpleUpdate =
534             res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
535         updateSvcSimpleUpdate["target"] =
536             "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
537         updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] = {
538             "TFTP"};
539 #endif
540         // Get the current ApplyTime value
541         crow::connections::systemBus->async_method_call(
542             [aResp](const boost::system::error_code ec,
543                     const std::variant<std::string>& applyTime) {
544                 if (ec)
545                 {
546                     BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
547                     messages::internalError(aResp->res);
548                     return;
549                 }
550 
551                 const std::string* s = std::get_if<std::string>(&applyTime);
552                 if (s == nullptr)
553                 {
554                     return;
555                 }
556                 // Store the ApplyTime Value
557                 if (*s == "xyz.openbmc_project.Software.ApplyTime."
558                           "RequestedApplyTimes.Immediate")
559                 {
560                     aResp->res.jsonValue["HttpPushUriOptions"]
561                                         ["HttpPushUriApplyTime"]["ApplyTime"] =
562                         "Immediate";
563                 }
564                 else if (*s == "xyz.openbmc_project.Software.ApplyTime."
565                                "RequestedApplyTimes.OnReset")
566                 {
567                     aResp->res.jsonValue["HttpPushUriOptions"]
568                                         ["HttpPushUriApplyTime"]["ApplyTime"] =
569                         "OnReset";
570                 }
571             },
572             "xyz.openbmc_project.Settings",
573             "/xyz/openbmc_project/software/apply_time",
574             "org.freedesktop.DBus.Properties", "Get",
575             "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime");
576     }
577 
578     void doPatch(crow::Response& res, const crow::Request& req,
579                  const std::vector<std::string>&) override
580     {
581         BMCWEB_LOG_DEBUG << "doPatch...";
582 
583         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
584 
585         std::optional<nlohmann::json> pushUriOptions;
586         if (!json_util::readJson(req, res, "HttpPushUriOptions",
587                                  pushUriOptions))
588         {
589             return;
590         }
591 
592         if (pushUriOptions)
593         {
594             std::optional<nlohmann::json> pushUriApplyTime;
595             if (!json_util::readJson(*pushUriOptions, res,
596                                      "HttpPushUriApplyTime", pushUriApplyTime))
597             {
598                 return;
599             }
600 
601             if (pushUriApplyTime)
602             {
603                 std::optional<std::string> applyTime;
604                 if (!json_util::readJson(*pushUriApplyTime, res, "ApplyTime",
605                                          applyTime))
606                 {
607                     return;
608                 }
609 
610                 if (applyTime)
611                 {
612                     std::string applyTimeNewVal;
613                     if (applyTime == "Immediate")
614                     {
615                         applyTimeNewVal =
616                             "xyz.openbmc_project.Software.ApplyTime."
617                             "RequestedApplyTimes.Immediate";
618                     }
619                     else if (applyTime == "OnReset")
620                     {
621                         applyTimeNewVal =
622                             "xyz.openbmc_project.Software.ApplyTime."
623                             "RequestedApplyTimes.OnReset";
624                     }
625                     else
626                     {
627                         BMCWEB_LOG_INFO
628                             << "ApplyTime value is not in the list of "
629                                "acceptable values";
630                         messages::propertyValueNotInList(
631                             asyncResp->res, *applyTime, "ApplyTime");
632                         return;
633                     }
634 
635                     // Set the requested image apply time value
636                     crow::connections::systemBus->async_method_call(
637                         [asyncResp](const boost::system::error_code ec) {
638                             if (ec)
639                             {
640                                 BMCWEB_LOG_ERROR << "D-Bus responses error: "
641                                                  << ec;
642                                 messages::internalError(asyncResp->res);
643                                 return;
644                             }
645                             messages::success(asyncResp->res);
646                         },
647                         "xyz.openbmc_project.Settings",
648                         "/xyz/openbmc_project/software/apply_time",
649                         "org.freedesktop.DBus.Properties", "Set",
650                         "xyz.openbmc_project.Software.ApplyTime",
651                         "RequestedApplyTime",
652                         std::variant<std::string>{applyTimeNewVal});
653                 }
654             }
655         }
656     }
657 
658     void doPost(crow::Response& res, const crow::Request& req,
659                 const std::vector<std::string>&) override
660     {
661         BMCWEB_LOG_DEBUG << "doPost...";
662 
663         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
664 
665         // Setup callback for when new software detected
666         monitorForSoftwareAvailable(asyncResp, req,
667                                     "/redfish/v1/UpdateService");
668 
669         std::string filepath(
670             "/tmp/images/" +
671             boost::uuids::to_string(boost::uuids::random_generator()()));
672         BMCWEB_LOG_DEBUG << "Writing file to " << filepath;
673         std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
674                                         std::ofstream::trunc);
675         out << req.body;
676         out.close();
677         BMCWEB_LOG_DEBUG << "file upload complete!!";
678     }
679 };
680 
681 class SoftwareInventoryCollection : public Node
682 {
683   public:
684     SoftwareInventoryCollection(App& app) :
685         Node(app, "/redfish/v1/UpdateService/FirmwareInventory/")
686     {
687         entityPrivileges = {
688             {boost::beast::http::verb::get, {{"Login"}}},
689             {boost::beast::http::verb::head, {{"Login"}}},
690             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
691             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
692             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
693             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
694     }
695 
696   private:
697     void doGet(crow::Response& res, const crow::Request&,
698                const std::vector<std::string>&) override
699     {
700         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
701         res.jsonValue["@odata.type"] =
702             "#SoftwareInventoryCollection.SoftwareInventoryCollection";
703         res.jsonValue["@odata.id"] =
704             "/redfish/v1/UpdateService/FirmwareInventory";
705         res.jsonValue["Name"] = "Software Inventory Collection";
706 
707         crow::connections::systemBus->async_method_call(
708             [asyncResp](
709                 const boost::system::error_code ec,
710                 const std::vector<std::pair<
711                     std::string, std::vector<std::pair<
712                                      std::string, std::vector<std::string>>>>>&
713                     subtree) {
714                 if (ec)
715                 {
716                     messages::internalError(asyncResp->res);
717                     return;
718                 }
719                 asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
720                 asyncResp->res.jsonValue["Members@odata.count"] = 0;
721 
722                 for (auto& obj : subtree)
723                 {
724                     // if can't parse fw id then return
725                     std::size_t idPos;
726                     if ((idPos = obj.first.rfind('/')) == std::string::npos)
727                     {
728                         messages::internalError(asyncResp->res);
729                         BMCWEB_LOG_DEBUG << "Can't parse firmware ID!!";
730                         return;
731                     }
732                     std::string swId = obj.first.substr(idPos + 1);
733 
734                     nlohmann::json& members =
735                         asyncResp->res.jsonValue["Members"];
736                     members.push_back(
737                         {{"@odata.id", "/redfish/v1/UpdateService/"
738                                        "FirmwareInventory/" +
739                                            swId}});
740                     asyncResp->res.jsonValue["Members@odata.count"] =
741                         members.size();
742                 }
743             },
744             // Note that only firmware levels associated with a device are
745             // stored under /xyz/openbmc_project/software therefore to ensure
746             // only real FirmwareInventory items are returned, this full object
747             // path must be used here as input to mapper
748             "xyz.openbmc_project.ObjectMapper",
749             "/xyz/openbmc_project/object_mapper",
750             "xyz.openbmc_project.ObjectMapper", "GetSubTree",
751             "/xyz/openbmc_project/software", static_cast<int32_t>(0),
752             std::array<const char*, 1>{"xyz.openbmc_project.Software.Version"});
753     }
754 };
755 
756 class SoftwareInventory : public Node
757 {
758   public:
759     SoftwareInventory(App& app) :
760         Node(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/",
761              std::string())
762     {
763         entityPrivileges = {
764             {boost::beast::http::verb::get, {{"Login"}}},
765             {boost::beast::http::verb::head, {{"Login"}}},
766             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
767             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
768             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
769             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
770     }
771 
772   private:
773     /* Fill related item links (i.e. bmc, bios) in for inventory */
774     static void getRelatedItems(const std::shared_ptr<AsyncResp>& aResp,
775                                 const std::string& purpose)
776     {
777         if (purpose == fw_util::bmcPurpose)
778         {
779             nlohmann::json& members = aResp->res.jsonValue["RelatedItem"];
780             members.push_back({{"@odata.id", "/redfish/v1/Managers/bmc"}});
781             aResp->res.jsonValue["Members@odata.count"] = members.size();
782         }
783         else if (purpose == fw_util::biosPurpose)
784         {
785             nlohmann::json& members = aResp->res.jsonValue["RelatedItem"];
786             members.push_back(
787                 {{"@odata.id", "/redfish/v1/Systems/system/Bios"}});
788             aResp->res.jsonValue["Members@odata.count"] = members.size();
789         }
790         else
791         {
792             BMCWEB_LOG_ERROR << "Unknown software purpose " << purpose;
793         }
794     }
795 
796     void doGet(crow::Response& res, const crow::Request&,
797                const std::vector<std::string>& params) override
798     {
799         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
800 
801         if (params.size() != 1)
802         {
803             messages::internalError(res);
804             res.end();
805             return;
806         }
807 
808         std::shared_ptr<std::string> swId =
809             std::make_shared<std::string>(params[0]);
810 
811         res.jsonValue["@odata.id"] =
812             "/redfish/v1/UpdateService/FirmwareInventory/" + *swId;
813 
814         crow::connections::systemBus->async_method_call(
815             [asyncResp, swId](
816                 const boost::system::error_code ec,
817                 const std::vector<std::pair<
818                     std::string, std::vector<std::pair<
819                                      std::string, std::vector<std::string>>>>>&
820                     subtree) {
821                 BMCWEB_LOG_DEBUG << "doGet callback...";
822                 if (ec)
823                 {
824                     messages::internalError(asyncResp->res);
825                     return;
826                 }
827 
828                 // Ensure we find our input swId, otherwise return an error
829                 bool found = false;
830                 for (const std::pair<
831                          std::string,
832                          std::vector<
833                              std::pair<std::string, std::vector<std::string>>>>&
834                          obj : subtree)
835                 {
836                     if (boost::ends_with(obj.first, *swId) != true)
837                     {
838                         continue;
839                     }
840 
841                     if (obj.second.size() < 1)
842                     {
843                         continue;
844                     }
845 
846                     found = true;
847                     fw_util::getFwStatus(asyncResp, swId, obj.second[0].first);
848 
849                     crow::connections::systemBus->async_method_call(
850                         [asyncResp,
851                          swId](const boost::system::error_code error_code,
852                                const boost::container::flat_map<
853                                    std::string, VariantType>& propertiesList) {
854                             if (error_code)
855                             {
856                                 messages::internalError(asyncResp->res);
857                                 return;
858                             }
859                             boost::container::flat_map<
860                                 std::string, VariantType>::const_iterator it =
861                                 propertiesList.find("Purpose");
862                             if (it == propertiesList.end())
863                             {
864                                 BMCWEB_LOG_DEBUG
865                                     << "Can't find property \"Purpose\"!";
866                                 messages::propertyMissing(asyncResp->res,
867                                                           "Purpose");
868                                 return;
869                             }
870                             const std::string* swInvPurpose =
871                                 std::get_if<std::string>(&it->second);
872                             if (swInvPurpose == nullptr)
873                             {
874                                 BMCWEB_LOG_DEBUG
875                                     << "wrong types for property\"Purpose\"!";
876                                 messages::propertyValueTypeError(asyncResp->res,
877                                                                  "", "Purpose");
878                                 return;
879                             }
880 
881                             BMCWEB_LOG_DEBUG << "swInvPurpose = "
882                                              << *swInvPurpose;
883                             it = propertiesList.find("Version");
884                             if (it == propertiesList.end())
885                             {
886                                 BMCWEB_LOG_DEBUG
887                                     << "Can't find property \"Version\"!";
888                                 messages::propertyMissing(asyncResp->res,
889                                                           "Version");
890                                 return;
891                             }
892 
893                             BMCWEB_LOG_DEBUG << "Version found!";
894 
895                             const std::string* version =
896                                 std::get_if<std::string>(&it->second);
897 
898                             if (version == nullptr)
899                             {
900                                 BMCWEB_LOG_DEBUG
901                                     << "Can't find property \"Version\"!";
902 
903                                 messages::propertyValueTypeError(asyncResp->res,
904                                                                  "", "Version");
905                                 return;
906                             }
907                             asyncResp->res.jsonValue["Version"] = *version;
908                             asyncResp->res.jsonValue["Id"] = *swId;
909 
910                             // swInvPurpose is of format:
911                             // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
912                             // Translate this to "ABC image"
913                             size_t endDesc = swInvPurpose->rfind('.');
914                             if (endDesc == std::string::npos)
915                             {
916                                 messages::internalError(asyncResp->res);
917                                 return;
918                             }
919                             endDesc++;
920                             if (endDesc >= swInvPurpose->size())
921                             {
922                                 messages::internalError(asyncResp->res);
923                                 return;
924                             }
925 
926                             std::string formatDesc =
927                                 swInvPurpose->substr(endDesc);
928                             asyncResp->res.jsonValue["Description"] =
929                                 formatDesc + " image";
930                             getRelatedItems(asyncResp, *swInvPurpose);
931                         },
932                         obj.second[0].first, obj.first,
933                         "org.freedesktop.DBus.Properties", "GetAll",
934                         "xyz.openbmc_project.Software.Version");
935                 }
936                 if (!found)
937                 {
938                     BMCWEB_LOG_ERROR << "Input swID " + *swId + " not found!";
939                     messages::resourceMissingAtURI(
940                         asyncResp->res,
941                         "/redfish/v1/UpdateService/FirmwareInventory/" + *swId);
942                     return;
943                 }
944                 asyncResp->res.jsonValue["@odata.type"] =
945                     "#SoftwareInventory.v1_1_0.SoftwareInventory";
946                 asyncResp->res.jsonValue["Name"] = "Software Inventory";
947                 asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
948 
949                 asyncResp->res.jsonValue["Updateable"] = false;
950                 fw_util::getFwUpdateableStatus(asyncResp, swId);
951             },
952             "xyz.openbmc_project.ObjectMapper",
953             "/xyz/openbmc_project/object_mapper",
954             "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/",
955             static_cast<int32_t>(0),
956             std::array<const char*, 1>{"xyz.openbmc_project.Software.Version"});
957     }
958 };
959 
960 } // namespace redfish
961