xref: /openbmc/bmcweb/redfish-core/lib/update_service.hpp (revision 96a194c4a8cbd2c10dd44650031c064eddf30252)
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 <algorithm>
54 #include <array>
55 #include <chrono>
56 #include <cstddef>
57 #include <cstdint>
58 #include <cstdio>
59 #include <filesystem>
60 #include <format>
61 #include <fstream>
62 #include <functional>
63 #include <memory>
64 #include <optional>
65 #include <string>
66 #include <string_view>
67 #include <unordered_map>
68 #include <utility>
69 #include <variant>
70 #include <vector>
71 
72 namespace redfish
73 {
74 
75 // Match signals added on software path
76 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
77 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateMatcher;
78 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
79 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateErrorMatcher;
80 // Only allow one update at a time
81 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
82 static bool fwUpdateInProgress = false;
83 // Timer for software available
84 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
85 static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer;
86 
87 /* @brief String that indicates the Software Update D-Bus interface */
88 constexpr const char* updateInterface = "xyz.openbmc_project.Software.Update";
89 
90 struct MemoryFileDescriptor
91 {
92     int fd = -1;
93 
MemoryFileDescriptorredfish::MemoryFileDescriptor94     explicit MemoryFileDescriptor(const std::string& filename) :
95         fd(memfd_create(filename.c_str(), 0))
96     {}
97 
98     MemoryFileDescriptor(const MemoryFileDescriptor&) = default;
MemoryFileDescriptorredfish::MemoryFileDescriptor99     MemoryFileDescriptor(MemoryFileDescriptor&& other) noexcept : fd(other.fd)
100     {
101         other.fd = -1;
102     }
103     MemoryFileDescriptor& operator=(const MemoryFileDescriptor&) = delete;
104     MemoryFileDescriptor& operator=(MemoryFileDescriptor&&) = default;
105 
~MemoryFileDescriptorredfish::MemoryFileDescriptor106     ~MemoryFileDescriptor()
107     {
108         if (fd != -1)
109         {
110             close(fd);
111         }
112     }
113 
rewindredfish::MemoryFileDescriptor114     bool rewind() const
115     {
116         if (lseek(fd, 0, SEEK_SET) == -1)
117         {
118             BMCWEB_LOG_ERROR("Failed to seek to beginning of image memfd");
119             return false;
120         }
121         return true;
122     }
123 };
124 
cleanUp()125 inline void cleanUp()
126 {
127     fwUpdateInProgress = false;
128     fwUpdateMatcher = nullptr;
129     fwUpdateErrorMatcher = nullptr;
130 }
131 
activateImage(const std::string & objPath,const std::string & service)132 inline void activateImage(const std::string& objPath,
133                           const std::string& service)
134 {
135     BMCWEB_LOG_DEBUG("Activate image for {} {}", objPath, service);
136     sdbusplus::asio::setProperty(
137         *crow::connections::systemBus, service, objPath,
138         "xyz.openbmc_project.Software.Activation", "RequestedActivation",
139         "xyz.openbmc_project.Software.Activation.RequestedActivations.Active",
140         [](const boost::system::error_code& ec) {
141             if (ec)
142             {
143                 BMCWEB_LOG_DEBUG("error_code = {}", ec);
144                 BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
145             }
146         });
147 }
148 
handleCreateTask(const boost::system::error_code & ec2,sdbusplus::message_t & msg,const std::shared_ptr<task::TaskData> & taskData)149 inline bool handleCreateTask(const boost::system::error_code& ec2,
150                              sdbusplus::message_t& msg,
151                              const std::shared_ptr<task::TaskData>& taskData)
152 {
153     if (ec2)
154     {
155         return task::completed;
156     }
157 
158     std::string iface;
159     dbus::utility::DBusPropertiesMap values;
160 
161     std::string index = std::to_string(taskData->index);
162     msg.read(iface, values);
163 
164     if (iface == "xyz.openbmc_project.Software.Activation")
165     {
166         const std::string* state = nullptr;
167         for (const auto& property : values)
168         {
169             if (property.first == "Activation")
170             {
171                 state = std::get_if<std::string>(&property.second);
172                 if (state == nullptr)
173                 {
174                     taskData->messages.emplace_back(messages::internalError());
175                     return task::completed;
176                 }
177             }
178         }
179 
180         if (state == nullptr)
181         {
182             return !task::completed;
183         }
184 
185         if (state->ends_with("Invalid") || state->ends_with("Failed"))
186         {
187             taskData->state = "Exception";
188             taskData->status = "Warning";
189             taskData->messages.emplace_back(messages::taskAborted(index));
190             return task::completed;
191         }
192 
193         if (state->ends_with("Staged"))
194         {
195             taskData->state = "Stopping";
196             taskData->messages.emplace_back(messages::taskPaused(index));
197 
198             // its staged, set a long timer to
199             // allow them time to complete the
200             // update (probably cycle the
201             // system) if this expires then
202             // task will be canceled
203             taskData->extendTimer(std::chrono::hours(5));
204             return !task::completed;
205         }
206 
207         if (state->ends_with("Active"))
208         {
209             taskData->messages.emplace_back(messages::taskCompletedOK(index));
210             taskData->state = "Completed";
211             return task::completed;
212         }
213     }
214     else if (iface == "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 = std::get_if<uint8_t>(&property.second);
222                 if (progress == nullptr)
223                 {
224                     taskData->messages.emplace_back(messages::internalError());
225                     return task::completed;
226                 }
227             }
228         }
229 
230         if (progress == nullptr)
231         {
232             return !task::completed;
233         }
234         taskData->percentComplete = *progress;
235         taskData->messages.emplace_back(
236             messages::taskProgressChanged(index, *progress));
237 
238         // if we're getting status updates it's
239         // still alive, update timer
240         taskData->extendTimer(std::chrono::minutes(5));
241     }
242 
243     // as firmware update often results in a
244     // reboot, the task  may never "complete"
245     // unless it is an error
246 
247     return !task::completed;
248 }
249 
createTask(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload && payload,const sdbusplus::message::object_path & objPath)250 inline void createTask(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
251                        task::Payload&& payload,
252                        const sdbusplus::message::object_path& objPath)
253 {
254     std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
255         std::bind_front(handleCreateTask),
256         "type='signal',interface='org.freedesktop.DBus.Properties',"
257         "member='PropertiesChanged',path='" +
258             objPath.str + "'");
259     task->startTimer(std::chrono::minutes(5));
260     task->payload.emplace(std::move(payload));
261     task->populateResp(asyncResp->res);
262 }
263 
264 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
265 // then no asyncResp updates will occur
softwareInterfaceAdded(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,sdbusplus::message_t & m,task::Payload && payload)266 inline void softwareInterfaceAdded(
267     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
268     sdbusplus::message_t& m, task::Payload&& payload)
269 {
270     dbus::utility::DBusInterfacesMap interfacesProperties;
271 
272     sdbusplus::message::object_path objPath;
273 
274     m.read(objPath, interfacesProperties);
275 
276     BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
277     for (const auto& interface : interfacesProperties)
278     {
279         BMCWEB_LOG_DEBUG("interface = {}", interface.first);
280 
281         if (interface.first == "xyz.openbmc_project.Software.Activation")
282         {
283             // Retrieve service and activate
284             constexpr std::array<std::string_view, 1> interfaces = {
285                 "xyz.openbmc_project.Software.Activation"};
286             dbus::utility::getDbusObject(
287                 objPath.str, interfaces,
288                 [objPath, asyncResp, payload(std::move(payload))](
289                     const boost::system::error_code& ec,
290                     const std::vector<
291                         std::pair<std::string, std::vector<std::string>>>&
292                         objInfo) mutable {
293                     if (ec)
294                     {
295                         BMCWEB_LOG_DEBUG("error_code = {}", ec);
296                         BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
297                         if (asyncResp)
298                         {
299                             messages::internalError(asyncResp->res);
300                         }
301                         cleanUp();
302                         return;
303                     }
304                     // Ensure we only got one service back
305                     if (objInfo.size() != 1)
306                     {
307                         BMCWEB_LOG_ERROR("Invalid Object Size {}",
308                                          objInfo.size());
309                         if (asyncResp)
310                         {
311                             messages::internalError(asyncResp->res);
312                         }
313                         cleanUp();
314                         return;
315                     }
316                     // cancel timer only when
317                     // xyz.openbmc_project.Software.Activation interface
318                     // is added
319                     fwAvailableTimer = nullptr;
320 
321                     activateImage(objPath.str, objInfo[0].first);
322                     if (asyncResp)
323                     {
324                         createTask(asyncResp, std::move(payload), objPath);
325                     }
326                     fwUpdateInProgress = false;
327                 });
328 
329             break;
330         }
331     }
332 }
333 
afterAvailbleTimerAsyncWait(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec)334 inline void afterAvailbleTimerAsyncWait(
335     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
336     const boost::system::error_code& ec)
337 {
338     cleanUp();
339     if (ec == boost::asio::error::operation_aborted)
340     {
341         // expected, we were canceled before the timer completed.
342         return;
343     }
344     BMCWEB_LOG_ERROR("Timed out waiting for firmware object being created");
345     BMCWEB_LOG_ERROR("FW image may has already been uploaded to server");
346     if (ec)
347     {
348         BMCWEB_LOG_ERROR("Async_wait failed{}", ec);
349         return;
350     }
351     if (asyncResp)
352     {
353         redfish::messages::internalError(asyncResp->res);
354     }
355 }
356 
handleUpdateErrorType(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & url,const std::string & type)357 inline void handleUpdateErrorType(
358     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& url,
359     const std::string& type)
360 {
361     // NOLINTBEGIN(bugprone-branch-clone)
362     if (type == "xyz.openbmc_project.Software.Image.Error.UnTarFailure")
363     {
364         messages::missingOrMalformedPart(asyncResp->res);
365     }
366     else if (type ==
367              "xyz.openbmc_project.Software.Image.Error.ManifestFileFailure")
368     {
369         messages::missingOrMalformedPart(asyncResp->res);
370     }
371     else if (type == "xyz.openbmc_project.Software.Image.Error.ImageFailure")
372     {
373         messages::missingOrMalformedPart(asyncResp->res);
374     }
375     else if (type == "xyz.openbmc_project.Software.Version.Error.AlreadyExists")
376     {
377         messages::resourceAlreadyExists(asyncResp->res, "UpdateService",
378                                         "Version", "uploaded version");
379     }
380     else if (type == "xyz.openbmc_project.Software.Image.Error.BusyFailure")
381     {
382         messages::serviceTemporarilyUnavailable(asyncResp->res, url);
383     }
384     else if (type == "xyz.openbmc_project.Software.Version.Error.Incompatible")
385     {
386         messages::internalError(asyncResp->res);
387     }
388     else if (type ==
389              "xyz.openbmc_project.Software.Version.Error.ExpiredAccessKey")
390     {
391         messages::internalError(asyncResp->res);
392     }
393     else if (type ==
394              "xyz.openbmc_project.Software.Version.Error.InvalidSignature")
395     {
396         messages::missingOrMalformedPart(asyncResp->res);
397     }
398     else if (type ==
399                  "xyz.openbmc_project.Software.Image.Error.InternalFailure" ||
400              type == "xyz.openbmc_project.Software.Version.Error.HostFile")
401     {
402         BMCWEB_LOG_ERROR("Software Image Error type={}", type);
403         messages::internalError(asyncResp->res);
404     }
405     else
406     {
407         // Unrelated error types. Ignored
408         BMCWEB_LOG_INFO("Non-Software-related Error type={}. Ignored", type);
409         return;
410     }
411     // NOLINTEND(bugprone-branch-clone)
412     // Clear the timer
413     fwAvailableTimer = nullptr;
414 }
415 
afterUpdateErrorMatcher(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & url,sdbusplus::message_t & m)416 inline void afterUpdateErrorMatcher(
417     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& url,
418     sdbusplus::message_t& m)
419 {
420     dbus::utility::DBusInterfacesMap interfacesProperties;
421     sdbusplus::message::object_path objPath;
422     m.read(objPath, interfacesProperties);
423     BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
424     for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>&
425              interface : interfacesProperties)
426     {
427         if (interface.first == "xyz.openbmc_project.Logging.Entry")
428         {
429             for (const std::pair<std::string, dbus::utility::DbusVariantType>&
430                      value : interface.second)
431             {
432                 if (value.first != "Message")
433                 {
434                     continue;
435                 }
436                 const std::string* type =
437                     std::get_if<std::string>(&value.second);
438                 if (type == nullptr)
439                 {
440                     // if this was our message, timeout will cover it
441                     return;
442                 }
443                 handleUpdateErrorType(asyncResp, url, *type);
444             }
445         }
446     }
447 }
448 
449 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
450 // 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)451 inline void monitorForSoftwareAvailable(
452     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
453     const crow::Request& req, const std::string& url,
454     int timeoutTimeSeconds = 50)
455 {
456     // Only allow one FW update at a time
457     if (fwUpdateInProgress)
458     {
459         if (asyncResp)
460         {
461             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
462         }
463         return;
464     }
465 
466     fwAvailableTimer =
467         std::make_unique<boost::asio::steady_timer>(getIoContext());
468 
469     fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds));
470 
471     fwAvailableTimer->async_wait(
472         std::bind_front(afterAvailbleTimerAsyncWait, asyncResp));
473 
474     task::Payload payload(req);
475     auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable {
476         BMCWEB_LOG_DEBUG("Match fired");
477         softwareInterfaceAdded(asyncResp, m, std::move(payload));
478     };
479 
480     fwUpdateInProgress = true;
481 
482     fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>(
483         *crow::connections::systemBus,
484         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
485         "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
486         callback);
487 
488     fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>(
489         *crow::connections::systemBus,
490         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
491         "member='InterfacesAdded',"
492         "path='/xyz/openbmc_project/logging'",
493         std::bind_front(afterUpdateErrorMatcher, asyncResp, url));
494 }
495 
parseSimpleUpdateUrl(std::string imageURI,std::optional<std::string> transferProtocol,crow::Response & res)496 inline std::optional<boost::urls::url> parseSimpleUpdateUrl(
497     std::string imageURI, std::optional<std::string> transferProtocol,
498     crow::Response& res)
499 {
500     if (imageURI.find("://") == std::string::npos)
501     {
502         if (imageURI.starts_with("/"))
503         {
504             messages::actionParameterValueTypeError(
505                 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
506             return std::nullopt;
507         }
508         if (!transferProtocol)
509         {
510             messages::actionParameterValueTypeError(
511                 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
512             return std::nullopt;
513         }
514         // OpenBMC currently only supports HTTPS
515         if (*transferProtocol == "HTTPS")
516         {
517             imageURI = "https://" + imageURI;
518         }
519         else
520         {
521             messages::actionParameterNotSupported(res, "TransferProtocol",
522                                                   *transferProtocol);
523             BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}",
524                              *transferProtocol);
525             return std::nullopt;
526         }
527     }
528 
529     boost::system::result<boost::urls::url> url =
530         boost::urls::parse_absolute_uri(imageURI);
531     if (!url)
532     {
533         messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
534                                                 "UpdateService.SimpleUpdate");
535 
536         return std::nullopt;
537     }
538     url->normalize();
539 
540     if (url->scheme() == "tftp")
541     {
542         if (url->encoded_path().size() < 2)
543         {
544             messages::actionParameterNotSupported(res, "ImageURI",
545                                                   url->buffer());
546             return std::nullopt;
547         }
548     }
549     else if (url->scheme() == "https")
550     {
551         // Empty paths default to "/"
552         if (url->encoded_path().empty())
553         {
554             url->set_encoded_path("/");
555         }
556     }
557     else
558     {
559         messages::actionParameterNotSupported(res, "ImageURI", imageURI);
560         return std::nullopt;
561     }
562 
563     if (url->encoded_path().empty())
564     {
565         messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
566                                                 "UpdateService.SimpleUpdate");
567         return std::nullopt;
568     }
569 
570     return *url;
571 }
572 
doHttpsUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::urls::url_view_base & url)573 inline void doHttpsUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
574                           const boost::urls::url_view_base& url)
575 {
576     messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
577                                           url.buffer());
578 }
579 
handleUpdateServiceSimpleUpdateAction(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)580 inline void handleUpdateServiceSimpleUpdateAction(
581     crow::App& app, const crow::Request& req,
582     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
583 {
584     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
585     {
586         return;
587     }
588 
589     std::optional<std::string> transferProtocol;
590     std::string imageURI;
591 
592     BMCWEB_LOG_DEBUG("Enter UpdateService.SimpleUpdate doPost");
593 
594     // User can pass in both TransferProtocol and ImageURI parameters or
595     // they can pass in just the ImageURI with the transfer protocol
596     // embedded within it.
597     // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin
598     // 2) ImageURI:tftp://1.1.1.1/myfile.bin
599 
600     if (!json_util::readJsonAction(              //
601             req, asyncResp->res,                 //
602             "ImageURI", imageURI,                //
603             "TransferProtocol", transferProtocol //
604             ))
605     {
606         BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter");
607         return;
608     }
609 
610     std::optional<boost::urls::url> url =
611         parseSimpleUpdateUrl(imageURI, transferProtocol, asyncResp->res);
612     if (!url)
613     {
614         return;
615     }
616     if (url->scheme() == "https")
617     {
618         doHttpsUpdate(asyncResp, *url);
619     }
620     else
621     {
622         messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
623                                               url->buffer());
624         return;
625     }
626 
627     BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost");
628 }
629 
uploadImageFile(crow::Response & res,std::string_view body)630 inline void uploadImageFile(crow::Response& res, std::string_view body)
631 {
632     std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID());
633 
634     BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string());
635     std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
636                                     std::ofstream::trunc);
637     // set the permission of the file to 640
638     std::filesystem::perms permission =
639         std::filesystem::perms::owner_read | std::filesystem::perms::group_read;
640     std::filesystem::permissions(filepath, permission);
641     out << body;
642 
643     if (out.bad())
644     {
645         messages::internalError(res);
646         cleanUp();
647     }
648 }
649 
650 // Convert the Request Apply Time to the D-Bus value
convertApplyTime(crow::Response & res,const std::string & applyTime,std::string & applyTimeNewVal)651 inline bool convertApplyTime(crow::Response& res, const std::string& applyTime,
652                              std::string& applyTimeNewVal)
653 {
654     if (applyTime == "Immediate")
655     {
656         applyTimeNewVal =
657             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
658     }
659     else if (applyTime == "OnReset")
660     {
661         applyTimeNewVal =
662             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
663     }
664     else
665     {
666         BMCWEB_LOG_WARNING(
667             "ApplyTime value {} is not in the list of acceptable values",
668             applyTime);
669         messages::propertyValueNotInList(res, applyTime, "ApplyTime");
670         return false;
671     }
672     return true;
673 }
674 
setApplyTime(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & applyTime)675 inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
676                          const std::string& applyTime)
677 {
678     std::string applyTimeNewVal;
679     if (!convertApplyTime(asyncResp->res, applyTime, applyTimeNewVal))
680     {
681         return;
682     }
683 
684     setDbusProperty(asyncResp, "ApplyTime", "xyz.openbmc_project.Settings",
685                     sdbusplus::message::object_path(
686                         "/xyz/openbmc_project/software/apply_time"),
687                     "xyz.openbmc_project.Software.ApplyTime",
688                     "RequestedApplyTime", applyTimeNewVal);
689 }
690 
691 struct MultiPartUpdate
692 {
693     std::string uploadData;
694     struct UpdateParameters
695     {
696         std::optional<std::string> applyTime;
697         std::optional<std::vector<std::string>> targets;
698     } params;
699 };
700 
processUrl(boost::system::result<boost::urls::url_view> & url)701 inline std::optional<std::string> processUrl(
702     boost::system::result<boost::urls::url_view>& url)
703 {
704     if (!url)
705     {
706         return std::nullopt;
707     }
708     if (crow::utility::readUrlSegments(*url, "redfish", "v1", "Managers",
709                                        BMCWEB_REDFISH_MANAGER_URI_NAME))
710     {
711         return std::make_optional(std::string(BMCWEB_REDFISH_MANAGER_URI_NAME));
712     }
713     if constexpr (!BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
714     {
715         return std::nullopt;
716     }
717     std::string firmwareId;
718     if (!crow::utility::readUrlSegments(*url, "redfish", "v1", "UpdateService",
719                                         "FirmwareInventory",
720                                         std::ref(firmwareId)))
721     {
722         return std::nullopt;
723     }
724 
725     return std::make_optional(firmwareId);
726 }
727 
parseFormPartName(const boost::beast::http::fields::const_iterator & contentDisposition)728 inline std::optional<std::string> parseFormPartName(
729     const boost::beast::http::fields::const_iterator& contentDisposition)
730 {
731     size_t semicolonPos = contentDisposition->value().find(';');
732     if (semicolonPos == std::string::npos)
733     {
734         return std::nullopt;
735     }
736 
737     for (const auto& param : boost::beast::http::param_list{
738              contentDisposition->value().substr(semicolonPos)})
739     {
740         if (param.first == "name" && !param.second.empty())
741         {
742             return std::string(param.second);
743         }
744     }
745     return std::nullopt;
746 }
747 
processUpdateParameters(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string_view content)748 inline std::optional<MultiPartUpdate::UpdateParameters> processUpdateParameters(
749     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
750     std::string_view content)
751 {
752     MultiPartUpdate::UpdateParameters multiRet;
753     nlohmann::json jsonContent = nlohmann::json::parse(content, nullptr, false);
754     if (jsonContent.is_discarded())
755     {
756         return std::nullopt;
757     }
758     nlohmann::json::object_t* obj =
759         jsonContent.get_ptr<nlohmann::json::object_t*>();
760     if (obj == nullptr)
761     {
762         messages::propertyValueTypeError(asyncResp->res, content,
763                                          "UpdateParameters");
764         return std::nullopt;
765     }
766 
767     if (!json_util::readJsonObject(                            //
768             *obj, asyncResp->res,                              //
769             "@Redfish.OperationApplyTime", multiRet.applyTime, //
770             "Targets", multiRet.targets                        //
771             ))
772     {
773         return std::nullopt;
774     }
775 
776     if (multiRet.targets)
777     {
778         if (multiRet.targets->size() > 1)
779         {
780             messages::propertyValueFormatError(asyncResp->res,
781                                                *multiRet.targets, "Targets");
782             return std::nullopt;
783         }
784 
785         for (auto& target : *multiRet.targets)
786         {
787             boost::system::result<boost::urls::url_view> url =
788                 boost::urls::parse_origin_form(target);
789             auto res = processUrl(url);
790             if (!res.has_value())
791             {
792                 messages::propertyValueFormatError(asyncResp->res, target,
793                                                    "Targets");
794                 return std::nullopt;
795             }
796             target = res.value();
797         }
798     }
799 
800     return multiRet;
801 }
802 
extractMultipartUpdateParameters(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,MultipartParser parser)803 inline std::optional<MultiPartUpdate> extractMultipartUpdateParameters(
804     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, MultipartParser parser)
805 {
806     MultiPartUpdate multiRet;
807     for (FormPart& formpart : parser.mime_fields)
808     {
809         boost::beast::http::fields::const_iterator it =
810             formpart.fields.find("Content-Disposition");
811         if (it == formpart.fields.end())
812         {
813             BMCWEB_LOG_ERROR("Couldn't find Content-Disposition");
814             return std::nullopt;
815         }
816         BMCWEB_LOG_INFO("Parsing value {}", it->value());
817 
818         auto formFieldNameOpt = parseFormPartName(it);
819         if (!formFieldNameOpt.has_value())
820         {
821             continue;
822         }
823 
824         const std::string& formFieldName = formFieldNameOpt.value();
825 
826         if (formFieldName == "UpdateParameters")
827         {
828             std::optional<MultiPartUpdate::UpdateParameters> params =
829                 processUpdateParameters(asyncResp, formpart.content);
830             if (!params)
831             {
832                 return std::nullopt;
833             }
834             multiRet.params = std::move(*params);
835         }
836         else if (formFieldName == "UpdateFile")
837         {
838             multiRet.uploadData = std::move(formpart.content);
839         }
840     }
841 
842     if (multiRet.uploadData.empty())
843     {
844         BMCWEB_LOG_ERROR("Upload data is NULL");
845         messages::propertyMissing(asyncResp->res, "UpdateFile");
846         return std::nullopt;
847     }
848 
849     return multiRet;
850 }
851 
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)852 inline void handleStartUpdate(
853     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
854     const std::string& objectPath, const boost::system::error_code& ec,
855     const sdbusplus::message::object_path& retPath)
856 {
857     if (ec)
858     {
859         BMCWEB_LOG_ERROR("error_code = {}", ec);
860         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
861         messages::internalError(asyncResp->res);
862         return;
863     }
864 
865     BMCWEB_LOG_INFO("Call to StartUpdate on {} Success, retPath = {}",
866                     objectPath, retPath.str);
867     createTask(asyncResp, std::move(payload), retPath);
868 }
869 
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)870 inline void startUpdate(
871     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
872     const MemoryFileDescriptor& memfd, const std::string& applyTime,
873     const std::string& objectPath, const std::string& serviceName)
874 {
875     dbus::utility::async_method_call(
876         asyncResp,
877         [asyncResp, payload = std::move(payload),
878          objectPath](const boost::system::error_code& ec1,
879                      const sdbusplus::message::object_path& retPath) mutable {
880             handleStartUpdate(asyncResp, std::move(payload), objectPath, ec1,
881                               retPath);
882         },
883         serviceName, objectPath, updateInterface, "StartUpdate",
884         sdbusplus::message::unix_fd(memfd.fd), applyTime);
885 }
886 
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)887 inline void getSwInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
888                       task::Payload payload, const MemoryFileDescriptor& memfd,
889                       const std::string& applyTime, const std::string& target,
890                       const boost::system::error_code& ec,
891                       const dbus::utility::MapperGetSubTreeResponse& subtree)
892 {
893     using SwInfoMap = std::unordered_map<
894         std::string, std::pair<sdbusplus::message::object_path, std::string>>;
895     SwInfoMap swInfoMap;
896 
897     if (ec)
898     {
899         BMCWEB_LOG_ERROR("error_code = {}", ec);
900         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
901         messages::internalError(asyncResp->res);
902         return;
903     }
904     BMCWEB_LOG_DEBUG("Found {} software version paths", subtree.size());
905 
906     for (const auto& entry : subtree)
907     {
908         sdbusplus::message::object_path path(entry.first);
909         std::string swId = path.filename();
910         swInfoMap.emplace(swId, make_pair(path, entry.second[0].first));
911     }
912 
913     auto swEntry = swInfoMap.find(target);
914     if (swEntry == swInfoMap.end())
915     {
916         BMCWEB_LOG_WARNING("No valid DBus path for Target URI {}", target);
917         messages::propertyValueFormatError(asyncResp->res, target, "Targets");
918         return;
919     }
920 
921     BMCWEB_LOG_DEBUG("Found software version path {} serviceName {}",
922                      swEntry->second.first.str, swEntry->second.second);
923 
924     startUpdate(asyncResp, std::move(payload), memfd, applyTime,
925                 swEntry->second.first.str, swEntry->second.second);
926 }
927 
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)928 inline void handleBMCUpdate(
929     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
930     const MemoryFileDescriptor& memfd, const std::string& applyTime,
931     const boost::system::error_code& ec,
932     const dbus::utility::MapperEndPoints& functionalSoftware)
933 {
934     if (ec)
935     {
936         BMCWEB_LOG_ERROR("error_code = {}", ec);
937         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
938         messages::internalError(asyncResp->res);
939         return;
940     }
941     if (functionalSoftware.size() != 1)
942     {
943         BMCWEB_LOG_ERROR("Found {} functional software endpoints",
944                          functionalSoftware.size());
945         messages::internalError(asyncResp->res);
946         return;
947     }
948 
949     startUpdate(asyncResp, std::move(payload), memfd, applyTime,
950                 functionalSoftware[0], "xyz.openbmc_project.Software.Manager");
951 }
952 
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)953 inline void handleMultipartManagerUpdate(
954     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
955     const MemoryFileDescriptor& memfd, const std::string& applyTime,
956     const boost::system::error_code& ec,
957     const dbus::utility::MapperGetSubTreeResponse& subtree)
958 {
959     if (ec)
960     {
961         BMCWEB_LOG_ERROR("error_code = {}", ec);
962         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
963         messages::internalError(asyncResp->res);
964         return;
965     }
966     if (subtree.size() != 1)
967     {
968         BMCWEB_LOG_ERROR("Found {} MultipartUpdate objects, expected exactly 1",
969                          subtree.size());
970         messages::internalError(asyncResp->res);
971         return;
972     }
973 
974     const auto& [objectPath, services] = subtree[0];
975     for (const auto& [serviceName, ifaces] : services)
976     {
977         if (std::ranges::find(ifaces, updateInterface) != 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