xref: /openbmc/bmcweb/redfish-core/lib/update_service.hpp (revision 9fbf8532d22f7d976c29fc62827fccc515ea31a6)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
4 #pragma once
5 
6 #include "bmcweb_config.h"
7 
8 #include "app.hpp"
9 #include "async_resp.hpp"
10 #include "dbus_singleton.hpp"
11 #include "dbus_utility.hpp"
12 #include "error_messages.hpp"
13 #include "generated/enums/resource.hpp"
14 #include "generated/enums/update_service.hpp"
15 #include "http_request.hpp"
16 #include "http_response.hpp"
17 #include "io_context_singleton.hpp"
18 #include "logging.hpp"
19 #include "multipart_parser.hpp"
20 #include "ossl_random.hpp"
21 #include "query.hpp"
22 #include "registries/privilege_registry.hpp"
23 #include "str_utility.hpp"
24 #include "task.hpp"
25 #include "task_messages.hpp"
26 #include "utility.hpp"
27 #include "utils/collection.hpp"
28 #include "utils/dbus_utils.hpp"
29 #include "utils/json_utils.hpp"
30 #include "utils/sw_utils.hpp"
31 
32 #include <sys/mman.h>
33 #include <unistd.h>
34 
35 #include <boost/asio/error.hpp>
36 #include <boost/asio/steady_timer.hpp>
37 #include <boost/beast/http/fields.hpp>
38 #include <boost/beast/http/status.hpp>
39 #include <boost/beast/http/verb.hpp>
40 #include <boost/system/error_code.hpp>
41 #include <boost/system/result.hpp>
42 #include <boost/url/format.hpp>
43 #include <boost/url/parse.hpp>
44 #include <boost/url/url.hpp>
45 #include <boost/url/url_view.hpp>
46 #include <boost/url/url_view_base.hpp>
47 #include <sdbusplus/asio/property.hpp>
48 #include <sdbusplus/bus/match.hpp>
49 #include <sdbusplus/message.hpp>
50 #include <sdbusplus/message/native_types.hpp>
51 #include <sdbusplus/unpack_properties.hpp>
52 
53 #include <array>
54 #include <chrono>
55 #include <cstddef>
56 #include <cstdint>
57 #include <cstdio>
58 #include <filesystem>
59 #include <format>
60 #include <fstream>
61 #include <functional>
62 #include <memory>
63 #include <optional>
64 #include <string>
65 #include <string_view>
66 #include <unordered_map>
67 #include <utility>
68 #include <variant>
69 #include <vector>
70 
71 namespace redfish
72 {
73 
74 // Match signals added on software path
75 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
76 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateMatcher;
77 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
78 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateErrorMatcher;
79 // Only allow one update at a time
80 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
81 static bool fwUpdateInProgress = false;
82 // Timer for software available
83 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
84 static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer;
85 
86 /* @brief String that indicates the Software Update D-Bus interface */
87 constexpr const char* updateInterface = "xyz.openbmc_project.Software.Update";
88 
89 struct MemoryFileDescriptor
90 {
91     int fd = -1;
92 
MemoryFileDescriptorredfish::MemoryFileDescriptor93     explicit MemoryFileDescriptor(const std::string& filename) :
94         fd(memfd_create(filename.c_str(), 0))
95     {}
96 
97     MemoryFileDescriptor(const MemoryFileDescriptor&) = default;
MemoryFileDescriptorredfish::MemoryFileDescriptor98     MemoryFileDescriptor(MemoryFileDescriptor&& other) noexcept : fd(other.fd)
99     {
100         other.fd = -1;
101     }
102     MemoryFileDescriptor& operator=(const MemoryFileDescriptor&) = delete;
103     MemoryFileDescriptor& operator=(MemoryFileDescriptor&&) = default;
104 
~MemoryFileDescriptorredfish::MemoryFileDescriptor105     ~MemoryFileDescriptor()
106     {
107         if (fd != -1)
108         {
109             close(fd);
110         }
111     }
112 
rewindredfish::MemoryFileDescriptor113     bool rewind() const
114     {
115         if (lseek(fd, 0, SEEK_SET) == -1)
116         {
117             BMCWEB_LOG_ERROR("Failed to seek to beginning of image memfd");
118             return false;
119         }
120         return true;
121     }
122 };
123 
cleanUp()124 inline void cleanUp()
125 {
126     fwUpdateInProgress = false;
127     fwUpdateMatcher = nullptr;
128     fwUpdateErrorMatcher = nullptr;
129 }
130 
activateImage(const std::string & objPath,const std::string & service)131 inline void activateImage(const std::string& objPath,
132                           const std::string& service)
133 {
134     BMCWEB_LOG_DEBUG("Activate image for {} {}", objPath, service);
135     sdbusplus::asio::setProperty(
136         *crow::connections::systemBus, service, objPath,
137         "xyz.openbmc_project.Software.Activation", "RequestedActivation",
138         "xyz.openbmc_project.Software.Activation.RequestedActivations.Active",
139         [](const boost::system::error_code& ec) {
140             if (ec)
141             {
142                 BMCWEB_LOG_DEBUG("error_code = {}", ec);
143                 BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
144             }
145         });
146 }
147 
handleCreateTask(const boost::system::error_code & ec2,sdbusplus::message_t & msg,const std::shared_ptr<task::TaskData> & taskData)148 inline bool handleCreateTask(const boost::system::error_code& ec2,
149                              sdbusplus::message_t& msg,
150                              const std::shared_ptr<task::TaskData>& taskData)
151 {
152     if (ec2)
153     {
154         return task::completed;
155     }
156 
157     std::string iface;
158     dbus::utility::DBusPropertiesMap values;
159 
160     std::string index = std::to_string(taskData->index);
161     msg.read(iface, values);
162 
163     if (iface == "xyz.openbmc_project.Software.Activation")
164     {
165         const std::string* state = nullptr;
166         for (const auto& property : values)
167         {
168             if (property.first == "Activation")
169             {
170                 state = std::get_if<std::string>(&property.second);
171                 if (state == nullptr)
172                 {
173                     taskData->messages.emplace_back(messages::internalError());
174                     return task::completed;
175                 }
176             }
177         }
178 
179         if (state == nullptr)
180         {
181             return !task::completed;
182         }
183 
184         if (state->ends_with("Invalid") || state->ends_with("Failed"))
185         {
186             taskData->state = "Exception";
187             taskData->status = "Warning";
188             taskData->messages.emplace_back(messages::taskAborted(index));
189             return task::completed;
190         }
191 
192         if (state->ends_with("Staged"))
193         {
194             taskData->state = "Stopping";
195             taskData->messages.emplace_back(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(messages::taskCompletedOK(index));
209             taskData->state = "Completed";
210             return task::completed;
211         }
212     }
213     else if (iface == "xyz.openbmc_project.Software.ActivationProgress")
214     {
215         const uint8_t* progress = nullptr;
216         for (const auto& property : values)
217         {
218             if (property.first == "Progress")
219             {
220                 progress = std::get_if<uint8_t>(&property.second);
221                 if (progress == nullptr)
222                 {
223                     taskData->messages.emplace_back(messages::internalError());
224                     return task::completed;
225                 }
226             }
227         }
228 
229         if (progress == nullptr)
230         {
231             return !task::completed;
232         }
233         taskData->percentComplete = *progress;
234         taskData->messages.emplace_back(
235             messages::taskProgressChanged(index, *progress));
236 
237         // if we're getting status updates it's
238         // still alive, update timer
239         taskData->extendTimer(std::chrono::minutes(5));
240     }
241 
242     // as firmware update often results in a
243     // reboot, the task  may never "complete"
244     // unless it is an error
245 
246     return !task::completed;
247 }
248 
createTask(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload && payload,const sdbusplus::message::object_path & objPath)249 inline void createTask(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
250                        task::Payload&& payload,
251                        const sdbusplus::message::object_path& objPath)
252 {
253     std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
254         std::bind_front(handleCreateTask),
255         "type='signal',interface='org.freedesktop.DBus.Properties',"
256         "member='PropertiesChanged',path='" +
257             objPath.str + "'");
258     task->startTimer(std::chrono::minutes(5));
259     task->payload.emplace(std::move(payload));
260     task->populateResp(asyncResp->res);
261 }
262 
263 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
264 // then no asyncResp updates will occur
softwareInterfaceAdded(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,sdbusplus::message_t & m,task::Payload && payload)265 inline void softwareInterfaceAdded(
266     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
267     sdbusplus::message_t& m, task::Payload&& payload)
268 {
269     dbus::utility::DBusInterfacesMap interfacesProperties;
270 
271     sdbusplus::message::object_path objPath;
272 
273     m.read(objPath, interfacesProperties);
274 
275     BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
276     for (const auto& interface : interfacesProperties)
277     {
278         BMCWEB_LOG_DEBUG("interface = {}", interface.first);
279 
280         if (interface.first == "xyz.openbmc_project.Software.Activation")
281         {
282             // Retrieve service and activate
283             constexpr std::array<std::string_view, 1> interfaces = {
284                 "xyz.openbmc_project.Software.Activation"};
285             dbus::utility::getDbusObject(
286                 objPath.str, interfaces,
287                 [objPath, asyncResp, payload(std::move(payload))](
288                     const boost::system::error_code& ec,
289                     const std::vector<
290                         std::pair<std::string, std::vector<std::string>>>&
291                         objInfo) mutable {
292                     if (ec)
293                     {
294                         BMCWEB_LOG_DEBUG("error_code = {}", ec);
295                         BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
296                         if (asyncResp)
297                         {
298                             messages::internalError(asyncResp->res);
299                         }
300                         cleanUp();
301                         return;
302                     }
303                     // Ensure we only got one service back
304                     if (objInfo.size() != 1)
305                     {
306                         BMCWEB_LOG_ERROR("Invalid Object Size {}",
307                                          objInfo.size());
308                         if (asyncResp)
309                         {
310                             messages::internalError(asyncResp->res);
311                         }
312                         cleanUp();
313                         return;
314                     }
315                     // cancel timer only when
316                     // xyz.openbmc_project.Software.Activation interface
317                     // is added
318                     fwAvailableTimer = nullptr;
319 
320                     activateImage(objPath.str, objInfo[0].first);
321                     if (asyncResp)
322                     {
323                         createTask(asyncResp, std::move(payload), objPath);
324                     }
325                     fwUpdateInProgress = false;
326                 });
327 
328             break;
329         }
330     }
331 }
332 
afterAvailbleTimerAsyncWait(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec)333 inline void afterAvailbleTimerAsyncWait(
334     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
335     const boost::system::error_code& ec)
336 {
337     cleanUp();
338     if (ec == boost::asio::error::operation_aborted)
339     {
340         // expected, we were canceled before the timer completed.
341         return;
342     }
343     BMCWEB_LOG_ERROR("Timed out waiting for firmware object being created");
344     BMCWEB_LOG_ERROR("FW image may has already been uploaded to server");
345     if (ec)
346     {
347         BMCWEB_LOG_ERROR("Async_wait failed{}", ec);
348         return;
349     }
350     if (asyncResp)
351     {
352         redfish::messages::internalError(asyncResp->res);
353     }
354 }
355 
handleUpdateErrorType(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & url,const std::string & type)356 inline void handleUpdateErrorType(
357     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& url,
358     const std::string& type)
359 {
360     // NOLINTBEGIN(bugprone-branch-clone)
361     if (type == "xyz.openbmc_project.Software.Image.Error.UnTarFailure")
362     {
363         messages::missingOrMalformedPart(asyncResp->res);
364     }
365     else if (type ==
366              "xyz.openbmc_project.Software.Image.Error.ManifestFileFailure")
367     {
368         messages::missingOrMalformedPart(asyncResp->res);
369     }
370     else if (type == "xyz.openbmc_project.Software.Image.Error.ImageFailure")
371     {
372         messages::missingOrMalformedPart(asyncResp->res);
373     }
374     else if (type == "xyz.openbmc_project.Software.Version.Error.AlreadyExists")
375     {
376         messages::resourceAlreadyExists(asyncResp->res, "UpdateService",
377                                         "Version", "uploaded version");
378     }
379     else if (type == "xyz.openbmc_project.Software.Image.Error.BusyFailure")
380     {
381         messages::serviceTemporarilyUnavailable(asyncResp->res, url);
382     }
383     else if (type == "xyz.openbmc_project.Software.Version.Error.Incompatible")
384     {
385         messages::internalError(asyncResp->res);
386     }
387     else if (type ==
388              "xyz.openbmc_project.Software.Version.Error.ExpiredAccessKey")
389     {
390         messages::internalError(asyncResp->res);
391     }
392     else if (type ==
393              "xyz.openbmc_project.Software.Version.Error.InvalidSignature")
394     {
395         messages::missingOrMalformedPart(asyncResp->res);
396     }
397     else if (type ==
398                  "xyz.openbmc_project.Software.Image.Error.InternalFailure" ||
399              type == "xyz.openbmc_project.Software.Version.Error.HostFile")
400     {
401         BMCWEB_LOG_ERROR("Software Image Error type={}", type);
402         messages::internalError(asyncResp->res);
403     }
404     else
405     {
406         // Unrelated error types. Ignored
407         BMCWEB_LOG_INFO("Non-Software-related Error type={}. Ignored", type);
408         return;
409     }
410     // NOLINTEND(bugprone-branch-clone)
411     // Clear the timer
412     fwAvailableTimer = nullptr;
413 }
414 
afterUpdateErrorMatcher(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & url,sdbusplus::message_t & m)415 inline void afterUpdateErrorMatcher(
416     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& url,
417     sdbusplus::message_t& m)
418 {
419     dbus::utility::DBusInterfacesMap interfacesProperties;
420     sdbusplus::message::object_path objPath;
421     m.read(objPath, interfacesProperties);
422     BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
423     for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>&
424              interface : interfacesProperties)
425     {
426         if (interface.first == "xyz.openbmc_project.Logging.Entry")
427         {
428             for (const std::pair<std::string, dbus::utility::DbusVariantType>&
429                      value : interface.second)
430             {
431                 if (value.first != "Message")
432                 {
433                     continue;
434                 }
435                 const std::string* type =
436                     std::get_if<std::string>(&value.second);
437                 if (type == nullptr)
438                 {
439                     // if this was our message, timeout will cover it
440                     return;
441                 }
442                 handleUpdateErrorType(asyncResp, url, *type);
443             }
444         }
445     }
446 }
447 
448 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
449 // then no asyncResp updates will occur
monitorForSoftwareAvailable(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const crow::Request & req,const std::string & url,int timeoutTimeSeconds=50)450 inline void monitorForSoftwareAvailable(
451     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
452     const crow::Request& req, const std::string& url,
453     int timeoutTimeSeconds = 50)
454 {
455     // Only allow one FW update at a time
456     if (fwUpdateInProgress)
457     {
458         if (asyncResp)
459         {
460             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
461         }
462         return;
463     }
464 
465     fwAvailableTimer =
466         std::make_unique<boost::asio::steady_timer>(getIoContext());
467 
468     fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds));
469 
470     fwAvailableTimer->async_wait(
471         std::bind_front(afterAvailbleTimerAsyncWait, asyncResp));
472 
473     task::Payload payload(req);
474     auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable {
475         BMCWEB_LOG_DEBUG("Match fired");
476         softwareInterfaceAdded(asyncResp, m, std::move(payload));
477     };
478 
479     fwUpdateInProgress = true;
480 
481     fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>(
482         *crow::connections::systemBus,
483         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
484         "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
485         callback);
486 
487     fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>(
488         *crow::connections::systemBus,
489         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
490         "member='InterfacesAdded',"
491         "path='/xyz/openbmc_project/logging'",
492         std::bind_front(afterUpdateErrorMatcher, asyncResp, url));
493 }
494 
parseSimpleUpdateUrl(std::string imageURI,std::optional<std::string> transferProtocol,crow::Response & res)495 inline std::optional<boost::urls::url> parseSimpleUpdateUrl(
496     std::string imageURI, std::optional<std::string> transferProtocol,
497     crow::Response& res)
498 {
499     if (imageURI.find("://") == std::string::npos)
500     {
501         if (imageURI.starts_with("/"))
502         {
503             messages::actionParameterValueTypeError(
504                 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
505             return std::nullopt;
506         }
507         if (!transferProtocol)
508         {
509             messages::actionParameterValueTypeError(
510                 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
511             return std::nullopt;
512         }
513         // OpenBMC currently only supports HTTPS
514         if (*transferProtocol == "HTTPS")
515         {
516             imageURI = "https://" + imageURI;
517         }
518         else
519         {
520             messages::actionParameterNotSupported(res, "TransferProtocol",
521                                                   *transferProtocol);
522             BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}",
523                              *transferProtocol);
524             return std::nullopt;
525         }
526     }
527 
528     boost::system::result<boost::urls::url> url =
529         boost::urls::parse_absolute_uri(imageURI);
530     if (!url)
531     {
532         messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
533                                                 "UpdateService.SimpleUpdate");
534 
535         return std::nullopt;
536     }
537     url->normalize();
538 
539     if (url->scheme() == "tftp")
540     {
541         if (url->encoded_path().size() < 2)
542         {
543             messages::actionParameterNotSupported(res, "ImageURI",
544                                                   url->buffer());
545             return std::nullopt;
546         }
547     }
548     else if (url->scheme() == "https")
549     {
550         // Empty paths default to "/"
551         if (url->encoded_path().empty())
552         {
553             url->set_encoded_path("/");
554         }
555     }
556     else
557     {
558         messages::actionParameterNotSupported(res, "ImageURI", imageURI);
559         return std::nullopt;
560     }
561 
562     if (url->encoded_path().empty())
563     {
564         messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
565                                                 "UpdateService.SimpleUpdate");
566         return std::nullopt;
567     }
568 
569     return *url;
570 }
571 
doHttpsUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::urls::url_view_base & url)572 inline void doHttpsUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
573                           const boost::urls::url_view_base& url)
574 {
575     messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
576                                           url.buffer());
577 }
578 
handleUpdateServiceSimpleUpdateAction(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)579 inline void handleUpdateServiceSimpleUpdateAction(
580     crow::App& app, const crow::Request& req,
581     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
582 {
583     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
584     {
585         return;
586     }
587 
588     std::optional<std::string> transferProtocol;
589     std::string imageURI;
590 
591     BMCWEB_LOG_DEBUG("Enter UpdateService.SimpleUpdate doPost");
592 
593     // User can pass in both TransferProtocol and ImageURI parameters or
594     // they can pass in just the ImageURI with the transfer protocol
595     // embedded within it.
596     // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin
597     // 2) ImageURI:tftp://1.1.1.1/myfile.bin
598 
599     if (!json_util::readJsonAction(              //
600             req, asyncResp->res,                 //
601             "ImageURI", imageURI,                //
602             "TransferProtocol", transferProtocol //
603             ))
604     {
605         BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter");
606         return;
607     }
608 
609     std::optional<boost::urls::url> url =
610         parseSimpleUpdateUrl(imageURI, transferProtocol, asyncResp->res);
611     if (!url)
612     {
613         return;
614     }
615     if (url->scheme() == "https")
616     {
617         doHttpsUpdate(asyncResp, *url);
618     }
619     else
620     {
621         messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
622                                               url->buffer());
623         return;
624     }
625 
626     BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost");
627 }
628 
uploadImageFile(crow::Response & res,std::string_view body)629 inline void uploadImageFile(crow::Response& res, std::string_view body)
630 {
631     std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID());
632 
633     BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string());
634     std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
635                                     std::ofstream::trunc);
636     // set the permission of the file to 640
637     std::filesystem::perms permission =
638         std::filesystem::perms::owner_read | std::filesystem::perms::group_read;
639     std::filesystem::permissions(filepath, permission);
640     out << body;
641 
642     if (out.bad())
643     {
644         messages::internalError(res);
645         cleanUp();
646     }
647 }
648 
649 // Convert the Request Apply Time to the D-Bus value
convertApplyTime(crow::Response & res,const std::string & applyTime,std::string & applyTimeNewVal)650 inline bool convertApplyTime(crow::Response& res, const std::string& applyTime,
651                              std::string& applyTimeNewVal)
652 {
653     if (applyTime == "Immediate")
654     {
655         applyTimeNewVal =
656             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
657     }
658     else if (applyTime == "OnReset")
659     {
660         applyTimeNewVal =
661             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
662     }
663     else
664     {
665         BMCWEB_LOG_WARNING(
666             "ApplyTime value {} is not in the list of acceptable values",
667             applyTime);
668         messages::propertyValueNotInList(res, applyTime, "ApplyTime");
669         return false;
670     }
671     return true;
672 }
673 
setApplyTime(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & applyTime)674 inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
675                          const std::string& applyTime)
676 {
677     std::string applyTimeNewVal;
678     if (!convertApplyTime(asyncResp->res, applyTime, applyTimeNewVal))
679     {
680         return;
681     }
682 
683     setDbusProperty(asyncResp, "ApplyTime", "xyz.openbmc_project.Settings",
684                     sdbusplus::message::object_path(
685                         "/xyz/openbmc_project/software/apply_time"),
686                     "xyz.openbmc_project.Software.ApplyTime",
687                     "RequestedApplyTime", applyTimeNewVal);
688 }
689 
690 struct MultiPartUpdate
691 {
692     std::string uploadData;
693     struct UpdateParameters
694     {
695         std::optional<std::string> applyTime;
696         std::optional<std::vector<std::string>> targets;
697     } params;
698 };
699 
processUrl(boost::system::result<boost::urls::url_view> & url)700 inline std::optional<std::string> processUrl(
701     boost::system::result<boost::urls::url_view>& url)
702 {
703     if (!url)
704     {
705         return std::nullopt;
706     }
707     if (crow::utility::readUrlSegments(*url, "redfish", "v1", "Managers",
708                                        BMCWEB_REDFISH_MANAGER_URI_NAME))
709     {
710         return std::make_optional(std::string(BMCWEB_REDFISH_MANAGER_URI_NAME));
711     }
712     if constexpr (!BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
713     {
714         return std::nullopt;
715     }
716     std::string firmwareId;
717     if (!crow::utility::readUrlSegments(*url, "redfish", "v1", "UpdateService",
718                                         "FirmwareInventory",
719                                         std::ref(firmwareId)))
720     {
721         return std::nullopt;
722     }
723 
724     return std::make_optional(firmwareId);
725 }
726 
parseFormPartName(const boost::beast::http::fields::const_iterator & contentDisposition)727 inline std::optional<std::string> parseFormPartName(
728     const boost::beast::http::fields::const_iterator& contentDisposition)
729 {
730     size_t semicolonPos = contentDisposition->value().find(';');
731     if (semicolonPos == std::string::npos)
732     {
733         return std::nullopt;
734     }
735 
736     for (const auto& param : boost::beast::http::param_list{
737              contentDisposition->value().substr(semicolonPos)})
738     {
739         if (param.first == "name" && !param.second.empty())
740         {
741             return std::string(param.second);
742         }
743     }
744     return std::nullopt;
745 }
746 
processUpdateParameters(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string_view content)747 inline std::optional<MultiPartUpdate::UpdateParameters> processUpdateParameters(
748     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
749     std::string_view content)
750 {
751     MultiPartUpdate::UpdateParameters multiRet;
752     nlohmann::json jsonContent = nlohmann::json::parse(content, nullptr, false);
753     if (jsonContent.is_discarded())
754     {
755         return std::nullopt;
756     }
757     nlohmann::json::object_t* obj =
758         jsonContent.get_ptr<nlohmann::json::object_t*>();
759     if (obj == nullptr)
760     {
761         messages::propertyValueTypeError(asyncResp->res, content,
762                                          "UpdateParameters");
763         return std::nullopt;
764     }
765 
766     if (!json_util::readJsonObject(                            //
767             *obj, asyncResp->res,                              //
768             "@Redfish.OperationApplyTime", multiRet.applyTime, //
769             "Targets", multiRet.targets                        //
770             ))
771     {
772         return std::nullopt;
773     }
774 
775     if (multiRet.targets)
776     {
777         if (multiRet.targets->size() > 1)
778         {
779             messages::propertyValueFormatError(asyncResp->res,
780                                                *multiRet.targets, "Targets");
781             return std::nullopt;
782         }
783 
784         for (auto& target : *multiRet.targets)
785         {
786             boost::system::result<boost::urls::url_view> url =
787                 boost::urls::parse_origin_form(target);
788             auto res = processUrl(url);
789             if (!res.has_value())
790             {
791                 messages::propertyValueFormatError(asyncResp->res, target,
792                                                    "Targets");
793                 return std::nullopt;
794             }
795             target = res.value();
796         }
797     }
798 
799     return multiRet;
800 }
801 
extractMultipartUpdateParameters(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,MultipartParser parser)802 inline std::optional<MultiPartUpdate> extractMultipartUpdateParameters(
803     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, MultipartParser parser)
804 {
805     MultiPartUpdate multiRet;
806     for (FormPart& formpart : parser.mime_fields)
807     {
808         boost::beast::http::fields::const_iterator it =
809             formpart.fields.find("Content-Disposition");
810         if (it == formpart.fields.end())
811         {
812             BMCWEB_LOG_ERROR("Couldn't find Content-Disposition");
813             return std::nullopt;
814         }
815         BMCWEB_LOG_INFO("Parsing value {}", it->value());
816 
817         auto formFieldNameOpt = parseFormPartName(it);
818         if (!formFieldNameOpt.has_value())
819         {
820             continue;
821         }
822 
823         const std::string& formFieldName = formFieldNameOpt.value();
824 
825         if (formFieldName == "UpdateParameters")
826         {
827             std::optional<MultiPartUpdate::UpdateParameters> params =
828                 processUpdateParameters(asyncResp, formpart.content);
829             if (!params)
830             {
831                 return std::nullopt;
832             }
833             multiRet.params = std::move(*params);
834         }
835         else if (formFieldName == "UpdateFile")
836         {
837             multiRet.uploadData = std::move(formpart.content);
838         }
839     }
840 
841     if (multiRet.uploadData.empty())
842     {
843         BMCWEB_LOG_ERROR("Upload data is NULL");
844         messages::propertyMissing(asyncResp->res, "UpdateFile");
845         return std::nullopt;
846     }
847 
848     return multiRet;
849 }
850 
handleStartUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload payload,const std::string & objectPath,const boost::system::error_code & ec,const sdbusplus::message::object_path & retPath)851 inline void handleStartUpdate(
852     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
853     const std::string& objectPath, const boost::system::error_code& ec,
854     const sdbusplus::message::object_path& retPath)
855 {
856     if (ec)
857     {
858         BMCWEB_LOG_ERROR("error_code = {}", ec);
859         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
860         messages::internalError(asyncResp->res);
861         return;
862     }
863 
864     BMCWEB_LOG_INFO("Call to StartUpdate on {} Success, retPath = {}",
865                     objectPath, retPath.str);
866     createTask(asyncResp, std::move(payload), retPath);
867 }
868 
startUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload payload,const MemoryFileDescriptor & memfd,const std::string & applyTime,const std::string & objectPath,const std::string & serviceName)869 inline void startUpdate(
870     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
871     const MemoryFileDescriptor& memfd, const std::string& applyTime,
872     const std::string& objectPath, const std::string& serviceName)
873 {
874     dbus::utility::async_method_call(
875         asyncResp,
876         [asyncResp, payload = std::move(payload),
877          objectPath](const boost::system::error_code& ec1,
878                      const sdbusplus::message::object_path& retPath) mutable {
879             handleStartUpdate(asyncResp, std::move(payload), objectPath, ec1,
880                               retPath);
881         },
882         serviceName, objectPath, updateInterface, "StartUpdate",
883         sdbusplus::message::unix_fd(memfd.fd), applyTime);
884 }
885 
getSwInfo(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload payload,const MemoryFileDescriptor & memfd,const std::string & applyTime,const std::string & target,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)886 inline void getSwInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
887                       task::Payload payload, const MemoryFileDescriptor& memfd,
888                       const std::string& applyTime, const std::string& target,
889                       const boost::system::error_code& ec,
890                       const dbus::utility::MapperGetSubTreeResponse& subtree)
891 {
892     using SwInfoMap = std::unordered_map<
893         std::string, std::pair<sdbusplus::message::object_path, std::string>>;
894     SwInfoMap swInfoMap;
895 
896     if (ec)
897     {
898         BMCWEB_LOG_ERROR("error_code = {}", ec);
899         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
900         messages::internalError(asyncResp->res);
901         return;
902     }
903     BMCWEB_LOG_DEBUG("Found {} software version paths", subtree.size());
904 
905     for (const auto& entry : subtree)
906     {
907         sdbusplus::message::object_path path(entry.first);
908         std::string swId = path.filename();
909         swInfoMap.emplace(swId, make_pair(path, entry.second[0].first));
910     }
911 
912     auto swEntry = swInfoMap.find(target);
913     if (swEntry == swInfoMap.end())
914     {
915         BMCWEB_LOG_WARNING("No valid DBus path for Target URI {}", target);
916         messages::propertyValueFormatError(asyncResp->res, target, "Targets");
917         return;
918     }
919 
920     BMCWEB_LOG_DEBUG("Found software version path {} serviceName {}",
921                      swEntry->second.first.str, swEntry->second.second);
922 
923     startUpdate(asyncResp, std::move(payload), memfd, applyTime,
924                 swEntry->second.first.str, swEntry->second.second);
925 }
926 
handleBMCUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload payload,const MemoryFileDescriptor & memfd,const std::string & applyTime,const boost::system::error_code & ec,const dbus::utility::MapperEndPoints & functionalSoftware)927 inline void handleBMCUpdate(
928     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
929     const MemoryFileDescriptor& memfd, const std::string& applyTime,
930     const boost::system::error_code& ec,
931     const dbus::utility::MapperEndPoints& functionalSoftware)
932 {
933     if (ec)
934     {
935         BMCWEB_LOG_ERROR("error_code = {}", ec);
936         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
937         messages::internalError(asyncResp->res);
938         return;
939     }
940     if (functionalSoftware.size() != 1)
941     {
942         BMCWEB_LOG_ERROR("Found {} functional software endpoints",
943                          functionalSoftware.size());
944         messages::internalError(asyncResp->res);
945         return;
946     }
947 
948     startUpdate(asyncResp, std::move(payload), memfd, applyTime,
949                 functionalSoftware[0], "xyz.openbmc_project.Software.Manager");
950 }
951 
handleMultipartManagerUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload payload,const MemoryFileDescriptor & memfd,const std::string & applyTime,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)952 inline void handleMultipartManagerUpdate(
953     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
954     const MemoryFileDescriptor& memfd, const std::string& applyTime,
955     const boost::system::error_code& ec,
956     const dbus::utility::MapperGetSubTreeResponse& subtree)
957 {
958     if (ec)
959     {
960         BMCWEB_LOG_ERROR("error_code = {}", ec);
961         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
962         messages::internalError(asyncResp->res);
963         return;
964     }
965     if (subtree.size() != 1)
966     {
967         BMCWEB_LOG_ERROR("Found {} MultipartUpdate objects, expected exactly 1",
968                          subtree.size());
969         messages::internalError(asyncResp->res);
970         return;
971     }
972 
973     const auto& [objectPath, services] = subtree[0];
974     for (const auto& [serviceName, ifaces] : services)
975     {
976         if (std::find(ifaces.begin(), ifaces.end(), updateInterface) !=
977             ifaces.end())
978         {
979             startUpdate(asyncResp, std::move(payload), memfd, applyTime,
980                         objectPath, serviceName);
981             return;
982         }
983     }
984     BMCWEB_LOG_ERROR(
985         "MultipartUpdate object at {} does not implement {} interface",
986         objectPath, updateInterface);
987     messages::internalError(asyncResp->res);
988 }
989 
processUpdateRequest(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload && payload,std::string_view body,const std::string & applyTime,const std::vector<std::string> & targets)990 inline void processUpdateRequest(
991     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
992     task::Payload&& payload, std::string_view body,
993     const std::string& applyTime, const std::vector<std::string>& targets)
994 {
995     MemoryFileDescriptor memfd("update-image");
996     if (memfd.fd == -1)
997     {
998         BMCWEB_LOG_ERROR("Failed to create image memfd");
999         messages::internalError(asyncResp->res);
1000         return;
1001     }
1002     if (write(memfd.fd, body.data(), body.length()) !=
1003         static_cast<ssize_t>(body.length()))
1004     {
1005         BMCWEB_LOG_ERROR("Failed to write to image memfd");
1006         messages::internalError(asyncResp->res);
1007         return;
1008     }
1009     if (!memfd.rewind())
1010     {
1011         messages::internalError(asyncResp->res);
1012         return;
1013     }
1014 
1015     if (targets.empty())
1016     {
1017         constexpr std::array<std::string_view, 1> interfaces = {
1018             "xyz.openbmc_project.Software.MultipartUpdate"};
1019         dbus::utility::getSubTree(
1020             "/xyz/openbmc_project/software", 1, interfaces,
1021             [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
1022              applyTime](const boost::system::error_code& ec,
1023                         const dbus::utility::MapperGetSubTreeResponse&
1024                             subtree) mutable {
1025                 handleMultipartManagerUpdate(asyncResp, std::move(payload),
1026                                              memfd, applyTime, ec, subtree);
1027             });
1028         return;
1029     }
1030 
1031     if (targets[0] == BMCWEB_REDFISH_MANAGER_URI_NAME)
1032     {
1033         dbus::utility::getAssociationEndPoints(
1034             "/xyz/openbmc_project/software/bmc/updateable",
1035             [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
1036              applyTime](
1037                 const boost::system::error_code& ec,
1038                 const dbus::utility::MapperEndPoints& objectPaths) mutable {
1039                 handleBMCUpdate(asyncResp, std::move(payload), memfd, applyTime,
1040                                 ec, objectPaths);
1041             });
1042     }
1043     else
1044     {
1045         constexpr std::array<std::string_view, 1> interfaces = {
1046             "xyz.openbmc_project.Software.Version"};
1047         dbus::utility::getSubTree(
1048             "/xyz/openbmc_project/software", 1, interfaces,
1049             [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
1050              applyTime, targets](const boost::system::error_code& ec,
1051                                  const dbus::utility::MapperGetSubTreeResponse&
1052                                      subtree) mutable {
1053                 getSwInfo(asyncResp, std::move(payload), memfd, applyTime,
1054                           targets[0], ec, subtree);
1055             });
1056     }
1057 }
1058 
updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const crow::Request & req,MultipartParser && parser)1059 inline void updateMultipartContext(
1060     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1061     const crow::Request& req, MultipartParser&& parser)
1062 {
1063     std::optional<MultiPartUpdate> multipart =
1064         extractMultipartUpdateParameters(asyncResp, std::move(parser));
1065     if (!multipart)
1066     {
1067         return;
1068     }
1069     if (!multipart->params.applyTime)
1070     {
1071         multipart->params.applyTime = "OnReset";
1072     }
1073 
1074     if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
1075     {
1076         std::string applyTimeNewVal;
1077         if (!convertApplyTime(asyncResp->res, *multipart->params.applyTime,
1078                               applyTimeNewVal))
1079         {
1080             return;
1081         }
1082         task::Payload payload(req);
1083 
1084         processUpdateRequest(
1085             asyncResp, std::move(payload), multipart->uploadData,
1086             applyTimeNewVal,
1087             multipart->params.targets.value_or(std::vector<std::string>{}));
1088     }
1089     else
1090     {
1091         setApplyTime(asyncResp, *multipart->params.applyTime);
1092 
1093         // Setup callback for when new software detected
1094         monitorForSoftwareAvailable(asyncResp, req,
1095                                     "/redfish/v1/UpdateService");
1096 
1097         uploadImageFile(asyncResp->res, multipart->uploadData);
1098     }
1099 }
1100 
doHTTPUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const crow::Request & req)1101 inline void doHTTPUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1102                          const crow::Request& req)
1103 {
1104     if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
1105     {
1106         task::Payload payload(req);
1107         // HTTP push only supports BMC updates (with ApplyTime as immediate) for
1108         // backwards compatibility. Specific component updates will be handled
1109         // through Multipart form HTTP push.
1110         std::vector<std::string> targets;
1111         targets.emplace_back(BMCWEB_REDFISH_MANAGER_URI_NAME);
1112 
1113         processUpdateRequest(
1114             asyncResp, std::move(payload), req.body(),
1115             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate",
1116             targets);
1117     }
1118     else
1119     {
1120         // Setup callback for when new software detected
1121         monitorForSoftwareAvailable(asyncResp, req,
1122                                     "/redfish/v1/UpdateService");
1123 
1124         uploadImageFile(asyncResp->res, req.body());
1125     }
1126 }
1127 
handleUpdateServicePost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1128 inline void handleUpdateServicePost(
1129     App& app, const crow::Request& req,
1130     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1131 {
1132     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1133     {
1134         return;
1135     }
1136     std::string_view contentType = req.getHeaderValue("Content-Type");
1137 
1138     BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType);
1139 
1140     // Make sure that content type is application/octet-stream
1141     if (bmcweb::asciiIEquals(contentType, "application/octet-stream"))
1142     {
1143         doHTTPUpdate(asyncResp, req);
1144     }
1145     else
1146     {
1147         BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType);
1148         asyncResp->res.result(boost::beast::http::status::bad_request);
1149     }
1150 }
1151 
handleUpdateServiceMultipartUpdatePost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1152 inline void handleUpdateServiceMultipartUpdatePost(
1153     App& app, const crow::Request& req,
1154     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1155 {
1156     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1157     {
1158         return;
1159     }
1160 
1161     std::string_view contentType = req.getHeaderValue("Content-Type");
1162     // Make sure that content type is multipart/form-data
1163     if (contentType.starts_with("multipart/form-data"))
1164     {
1165         MultipartParser parser;
1166 
1167         ParserError ec = parser.parse(req);
1168         if (ec != ParserError::PARSER_SUCCESS)
1169         {
1170             // handle error
1171             BMCWEB_LOG_ERROR("MIME parse failed, ec : {}",
1172                              static_cast<int>(ec));
1173             messages::internalError(asyncResp->res);
1174             return;
1175         }
1176 
1177         updateMultipartContext(asyncResp, req, std::move(parser));
1178     }
1179     else
1180     {
1181         BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType);
1182         asyncResp->res.result(
1183             boost::beast::http::status::unsupported_media_type);
1184     }
1185 }
1186 
handleUpdateServiceGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1187 inline void handleUpdateServiceGet(
1188     App& app, const crow::Request& req,
1189     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1190 {
1191     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1192     {
1193         return;
1194     }
1195     asyncResp->res.jsonValue["@odata.type"] =
1196         "#UpdateService.v1_11_1.UpdateService";
1197     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
1198     asyncResp->res.jsonValue["Id"] = "UpdateService";
1199     asyncResp->res.jsonValue["Description"] = "Service for Software Update";
1200     asyncResp->res.jsonValue["Name"] = "Update Service";
1201 
1202     asyncResp->res.jsonValue["HttpPushUri"] =
1203         "/redfish/v1/UpdateService/update";
1204     asyncResp->res.jsonValue["MultipartHttpPushUri"] =
1205         "/redfish/v1/UpdateService/update-multipart";
1206 
1207     // UpdateService cannot be disabled
1208     asyncResp->res.jsonValue["ServiceEnabled"] = true;
1209     asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] =
1210         "/redfish/v1/UpdateService/FirmwareInventory";
1211     // Get the MaxImageSizeBytes
1212     asyncResp->res.jsonValue["MaxImageSizeBytes"] =
1213         BMCWEB_HTTP_BODY_LIMIT * 1024 * 1024;
1214 
1215     if constexpr (BMCWEB_REDFISH_ALLOW_SIMPLE_UPDATE)
1216     {
1217         // Update Actions object.
1218         nlohmann::json& updateSvcSimpleUpdate =
1219             asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
1220         updateSvcSimpleUpdate["target"] =
1221             "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
1222 
1223         nlohmann::json::array_t allowed;
1224         allowed.emplace_back(update_service::TransferProtocolType::HTTPS);
1225         updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] =
1226             std::move(allowed);
1227     }
1228 
1229     asyncResp->res
1230         .jsonValue["HttpPushUriOptions"]["HttpPushUriApplyTime"]["ApplyTime"] =
1231         update_service::ApplyTime::Immediate;
1232 }
1233 
handleUpdateServiceFirmwareInventoryCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1234 inline void handleUpdateServiceFirmwareInventoryCollectionGet(
1235     App& app, const crow::Request& req,
1236     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1237 {
1238     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1239     {
1240         return;
1241     }
1242     asyncResp->res.jsonValue["@odata.type"] =
1243         "#SoftwareInventoryCollection.SoftwareInventoryCollection";
1244     asyncResp->res.jsonValue["@odata.id"] =
1245         "/redfish/v1/UpdateService/FirmwareInventory";
1246     asyncResp->res.jsonValue["Name"] = "Software Inventory Collection";
1247     const std::array<const std::string_view, 1> iface = {
1248         "xyz.openbmc_project.Software.Version"};
1249 
1250     redfish::collection_util::getCollectionMembers(
1251         asyncResp,
1252         boost::urls::url("/redfish/v1/UpdateService/FirmwareInventory"), iface,
1253         "/xyz/openbmc_project/software");
1254 }
1255 
addRelatedItem(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::urls::url & url)1256 inline void addRelatedItem(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1257                            const boost::urls::url& url)
1258 {
1259     nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
1260     nlohmann::json::object_t item;
1261     item["@odata.id"] = url;
1262     relatedItem.emplace_back(std::move(item));
1263     asyncResp->res.jsonValue["RelatedItem@odata.count"] = relatedItem.size();
1264 }
1265 
1266 /* Fill related item links (i.e. bmc, bios) in for inventory */
getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & purpose)1267 inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1268                             const std::string& purpose)
1269 {
1270     if (purpose == sw_util::bmcPurpose)
1271     {
1272         auto url = boost::urls::format("/redfish/v1/Managers/{}",
1273                                        BMCWEB_REDFISH_MANAGER_URI_NAME);
1274         addRelatedItem(asyncResp, url);
1275     }
1276     else if (purpose == sw_util::biosPurpose)
1277     {
1278         auto url = boost::urls::format("/redfish/v1/Systems/{}/Bios",
1279                                        BMCWEB_REDFISH_SYSTEM_URI_NAME);
1280 
1281         addRelatedItem(asyncResp, url);
1282     }
1283     else
1284     {
1285         BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose);
1286     }
1287 }
1288 
getSoftwareVersionCallback(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & swId,const boost::system::error_code & ec,const dbus::utility::DBusPropertiesMap & propertiesList)1289 inline void getSoftwareVersionCallback(
1290     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1291     const std::string& swId, const boost::system::error_code& ec,
1292     const dbus::utility::DBusPropertiesMap& propertiesList)
1293 {
1294     if (ec)
1295     {
1296         messages::internalError(asyncResp->res);
1297         return;
1298     }
1299     const std::string* swInvPurpose = nullptr;
1300     const std::string* version = nullptr;
1301     const bool success = sdbusplus::unpackPropertiesNoThrow(
1302         dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose",
1303         swInvPurpose, "Version", version);
1304     if (!success)
1305     {
1306         messages::internalError(asyncResp->res);
1307         return;
1308     }
1309     if (swInvPurpose == nullptr)
1310     {
1311         BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!");
1312         messages::internalError(asyncResp->res);
1313         return;
1314     }
1315     BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose);
1316     if (version == nullptr)
1317     {
1318         BMCWEB_LOG_DEBUG("Can't find property \"Version\"!");
1319         messages::internalError(asyncResp->res);
1320         return;
1321     }
1322     asyncResp->res.jsonValue["Version"] = *version;
1323     asyncResp->res.jsonValue["Id"] = swId;
1324     // swInvPurpose is of format:
1325     // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
1326     // Translate this to "ABC image"
1327     size_t endDesc = swInvPurpose->rfind('.');
1328     if (endDesc == std::string::npos)
1329     {
1330         messages::internalError(asyncResp->res);
1331         return;
1332     }
1333     endDesc++;
1334     if (endDesc >= swInvPurpose->size())
1335     {
1336         messages::internalError(asyncResp->res);
1337         return;
1338     }
1339     std::string formatDesc = swInvPurpose->substr(endDesc);
1340     asyncResp->res.jsonValue["Description"] = formatDesc + " image";
1341     getRelatedItems(asyncResp, *swInvPurpose);
1342 }
1343 
getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & service,const std::string & path,const std::string & swId)1344 inline void getSoftwareVersion(
1345     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1346     const std::string& service, const std::string& path,
1347     const std::string& swId)
1348 {
1349     dbus::utility::getAllProperties(
1350         service, path, "xyz.openbmc_project.Software.Version",
1351         std::bind_front(getSoftwareVersionCallback, asyncResp, swId));
1352 }
1353 
handleUpdateServiceFirmwareInventoryGetCallback(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::shared_ptr<std::string> & swId,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)1354 inline void handleUpdateServiceFirmwareInventoryGetCallback(
1355     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1356     const std::shared_ptr<std::string>& swId,
1357     const boost::system::error_code& ec,
1358     const dbus::utility::MapperGetSubTreeResponse& subtree)
1359 {
1360     BMCWEB_LOG_DEBUG("doGet callback...");
1361     if (ec)
1362     {
1363         messages::internalError(asyncResp->res);
1364         return;
1365     }
1366     // Ensure we find our input swId, otherwise return an error
1367     bool found = false;
1368     for (const std::pair<
1369              std::string,
1370              std::vector<std::pair<std::string, std::vector<std::string>>>>&
1371              obj : subtree)
1372     {
1373         sdbusplus::message::object_path path(obj.first);
1374         std::string id = path.filename();
1375         if (id.empty())
1376         {
1377             BMCWEB_LOG_DEBUG("Failed to find software id in {}", obj.first);
1378             continue;
1379         }
1380         if (id != *swId)
1381         {
1382             continue;
1383         }
1384         if (obj.second.empty())
1385         {
1386             continue;
1387         }
1388         found = true;
1389         sw_util::getSwStatus(asyncResp, swId, obj.second[0].first);
1390         sw_util::getSwMinimumVersion(asyncResp, swId, obj.second[0].first);
1391         getSoftwareVersion(asyncResp, obj.second[0].first, obj.first, *swId);
1392     }
1393     if (!found)
1394     {
1395         BMCWEB_LOG_WARNING("Input swID {} not found!", *swId);
1396         messages::resourceMissingAtURI(
1397             asyncResp->res,
1398             boost::urls::format(
1399                 "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId));
1400         return;
1401     }
1402     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
1403         "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId);
1404     asyncResp->res.jsonValue["@odata.type"] =
1405         "#SoftwareInventory.v1_1_0.SoftwareInventory";
1406     asyncResp->res.jsonValue["Name"] = "Software Inventory";
1407     asyncResp->res.jsonValue["Status"]["HealthRollup"] = resource::Health::OK;
1408     asyncResp->res.jsonValue["Updateable"] = false;
1409     sw_util::getSwUpdatableStatus(asyncResp, swId);
1410 }
1411 
handleUpdateServiceFirmwareInventoryGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & param)1412 inline void handleUpdateServiceFirmwareInventoryGet(
1413     App& app, const crow::Request& req,
1414     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1415     const std::string& param)
1416 {
1417     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1418     {
1419         return;
1420     }
1421     std::shared_ptr<std::string> swId = std::make_shared<std::string>(param);
1422 
1423     constexpr std::array<std::string_view, 1> interfaces = {
1424         "xyz.openbmc_project.Software.Version"};
1425     dbus::utility::getSubTree(
1426         "/xyz/openbmc_project/software/", 0, interfaces,
1427         std::bind_front(handleUpdateServiceFirmwareInventoryGetCallback,
1428                         asyncResp, swId));
1429 }
1430 
requestRoutesUpdateService(App & app)1431 inline void requestRoutesUpdateService(App& app)
1432 {
1433     if constexpr (BMCWEB_REDFISH_ALLOW_SIMPLE_UPDATE)
1434     {
1435         BMCWEB_ROUTE(
1436             app,
1437             "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/")
1438             .privileges(redfish::privileges::postUpdateService)
1439             .methods(boost::beast::http::verb::post)(std::bind_front(
1440                 handleUpdateServiceSimpleUpdateAction, std::ref(app)));
1441     }
1442     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
1443         .privileges(redfish::privileges::getSoftwareInventory)
1444         .methods(boost::beast::http::verb::get)(std::bind_front(
1445             handleUpdateServiceFirmwareInventoryGet, std::ref(app)));
1446 
1447     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
1448         .privileges(redfish::privileges::getUpdateService)
1449         .methods(boost::beast::http::verb::get)(
1450             std::bind_front(handleUpdateServiceGet, std::ref(app)));
1451 
1452     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/")
1453         .privileges(redfish::privileges::postUpdateService)
1454         .methods(boost::beast::http::verb::post)(
1455             std::bind_front(handleUpdateServicePost, std::ref(app)));
1456 
1457     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update-multipart/")
1458         .privileges(redfish::privileges::postUpdateService)
1459         .methods(boost::beast::http::verb::post)(std::bind_front(
1460             handleUpdateServiceMultipartUpdatePost, std::ref(app)));
1461 
1462     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/")
1463         .privileges(redfish::privileges::getSoftwareInventoryCollection)
1464         .methods(boost::beast::http::verb::get)(std::bind_front(
1465             handleUpdateServiceFirmwareInventoryCollectionGet, std::ref(app)));
1466 }
1467 
1468 } // namespace redfish
1469