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