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