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