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