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