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