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