1 #include "update_manager.hpp"
2 
3 #include "item_updater.hpp"
4 #include "software_utils.hpp"
5 #include "version.hpp"
6 
7 #include <phosphor-logging/elog-errors.hpp>
8 #include <phosphor-logging/elog.hpp>
9 #include <phosphor-logging/lg2.hpp>
10 #include <sdbusplus/async.hpp>
11 #include <xyz/openbmc_project/Common/error.hpp>
12 #include <xyz/openbmc_project/Software/Image/error.hpp>
13 
14 #include <filesystem>
15 
16 PHOSPHOR_LOG2_USING;
17 
18 namespace phosphor::software::update
19 {
20 
21 namespace fs = std::filesystem;
22 namespace softwareUtils = phosphor::software::utils;
23 namespace SoftwareLogging = phosphor::logging::xyz::openbmc_project::software;
24 namespace SoftwareErrors =
25     sdbusplus::error::xyz::openbmc_project::software::image;
26 using namespace phosphor::logging;
27 using Version = phosphor::software::manager::Version;
28 using ActivationIntf = phosphor::software::updater::Activation;
29 using ManifestFail = SoftwareLogging::image::ManifestFileFailure;
30 using UnTarFail = SoftwareLogging::image::UnTarFailure;
31 using InternalFail = SoftwareLogging::image::InternalFailure;
32 using ImageFail = SoftwareLogging::image::ImageFailure;
33 
34 void Manager::processImageFailed(sdbusplus::message::unix_fd image,
35                                  std::string& id)
36 {
37     close(image);
38     updateInProgress = false;
39     itemUpdater.updateActivationStatus(id,
40                                        ActivationIntf::Activations::Invalid);
41 }
42 
43 bool verifyImagePurpose(Version::VersionPurpose purpose,
44                         ItemUpdaterIntf::UpdaterType type)
45 {
46     if (purpose == Version::VersionPurpose::Host)
47     {
48         return (type == ItemUpdaterIntf::UpdaterType::BIOS ||
49                 type == ItemUpdaterIntf::UpdaterType::ALL);
50     }
51     return true;
52 }
53 
54 // NOLINTNEXTLINE(readability-static-accessed-through-instance)
55 auto Manager::processImage(sdbusplus::message::unix_fd image,
56                            ApplyTimeIntf::RequestedApplyTimes applyTime,
57                            std::string id,
58                            std::string objPath) -> sdbusplus::async::task<>
59 {
60     debug("Processing image {FD}", "FD", image.fd);
61     fs::path tmpDirPath(std::string{IMG_UPLOAD_DIR});
62     tmpDirPath /= "imageXXXXXX";
63     auto tmpDir = tmpDirPath.string();
64     // Create a tmp dir to copy tarball.
65     if (!mkdtemp(tmpDir.data()))
66     {
67         error("Error ({ERRNO}) occurred during mkdtemp", "ERRNO", errno);
68         processImageFailed(image, id);
69         report<SoftwareErrors::InternalFailure>(InternalFail::FAIL("mkdtemp"));
70         co_return;
71     }
72 
73     std::error_code ec;
74     tmpDirPath = tmpDir;
75     softwareUtils::RemovablePath tmpDirToRemove(tmpDirPath);
76 
77     // Untar tarball into the tmp dir
78     if (!softwareUtils::unTar(image, tmpDirPath.string()))
79     {
80         error("Error occurred during untar");
81         processImageFailed(image, id);
82         report<SoftwareErrors::UnTarFailure>(
83             UnTarFail::PATH(tmpDirPath.c_str()));
84         co_return;
85     }
86 
87     fs::path manifestPath = tmpDirPath;
88     manifestPath /= MANIFEST_FILE_NAME;
89 
90     // Get version
91     auto version = Version::getValue(manifestPath.string(), "version");
92     if (version.empty())
93     {
94         error("Unable to read version from manifest file");
95         processImageFailed(image, id);
96         report<SoftwareErrors::ManifestFileFailure>(
97             ManifestFail::PATH(manifestPath.string().c_str()));
98         co_return;
99     }
100 
101     // Get running machine name
102     std::string currMachine = Version::getBMCMachine(OS_RELEASE_FILE);
103     if (currMachine.empty())
104     {
105         auto path = OS_RELEASE_FILE;
106         error("Failed to read machine name from osRelease: {PATH}", "PATH",
107               path);
108         processImageFailed(image, id);
109         report<SoftwareErrors::ImageFailure>(
110             ImageFail::FAIL("Failed to read machine name"),
111             ImageFail::PATH(path));
112         co_return;
113     }
114 
115     // Get machine name for image to be upgraded
116     std::string machineStr =
117         Version::getValue(manifestPath.string(), "MachineName");
118     if (!machineStr.empty())
119     {
120         if (machineStr != currMachine)
121         {
122             error(
123                 "BMC upgrade: Machine name doesn't match: {CURRENT_MACHINE} vs {NEW_MACHINE}",
124                 "CURRENT_MACHINE", currMachine, "NEW_MACHINE", machineStr);
125             processImageFailed(image, id);
126             report<SoftwareErrors::ImageFailure>(
127                 ImageFail::FAIL("Machine name does not match"),
128                 ImageFail::PATH(manifestPath.string().c_str()));
129             co_return;
130         }
131     }
132     else
133     {
134         warning("No machine name in Manifest file");
135         report<SoftwareErrors::ImageFailure>(
136             ImageFail::FAIL("MANIFEST is missing machine name"),
137             ImageFail::PATH(manifestPath.string().c_str()));
138     }
139 
140     // Get purpose
141     auto purposeString = Version::getValue(manifestPath.string(), "purpose");
142     if (purposeString.empty())
143     {
144         error("Unable to read purpose from manifest file");
145         processImageFailed(image, id);
146         report<SoftwareErrors::ManifestFileFailure>(
147             ManifestFail::PATH(manifestPath.string().c_str()));
148         co_return;
149     }
150     auto convertedPurpose =
151         sdbusplus::message::convert_from_string<Version::VersionPurpose>(
152             purposeString);
153     if (!convertedPurpose)
154     {
155         warning(
156             "Failed to convert manifest purpose ({PURPOSE}) to enum; setting to Unknown.",
157             "PURPOSE", purposeString);
158     }
159     auto purpose = convertedPurpose.value_or(Version::VersionPurpose::Unknown);
160 
161     if (!verifyImagePurpose(purpose, itemUpdater.type))
162     {
163         error("Purpose ({PURPOSE}) is not supported", "PURPOSE", purpose);
164         processImageFailed(image, id);
165         report<SoftwareErrors::ImageFailure>(
166             ImageFail::FAIL("Purpose is not supported"),
167             ImageFail::PATH(manifestPath.string().c_str()));
168         co_return;
169     }
170 
171     // Get ExtendedVersion
172     std::string extendedVersion =
173         Version::getValue(manifestPath.string(), "ExtendedVersion");
174 
175     // Get CompatibleNames
176     std::vector<std::string> compatibleNames =
177         Version::getRepeatedValues(manifestPath.string(), "CompatibleName");
178 
179     // Rename IMG_UPLOAD_DIR/imageXXXXXX to IMG_UPLOAD_DIR/id as Manifest
180     // parsing succedded.
181     fs::path imageDirPath = std::string{IMG_UPLOAD_DIR};
182     imageDirPath /= id;
183     fs::rename(tmpDirPath, imageDirPath, ec);
184     tmpDirToRemove.path.clear();
185 
186     auto filePath = imageDirPath.string();
187     // Create Version object
188     auto state = itemUpdater.verifyAndCreateObjects(
189         id, objPath, version, purpose, extendedVersion, filePath,
190         compatibleNames);
191     if (state != ActivationIntf::Activations::Ready)
192     {
193         error("Software image is invalid");
194         processImageFailed(image, id);
195         report<SoftwareErrors::ImageFailure>(
196             ImageFail::FAIL("Image is invalid"),
197             ImageFail::PATH(filePath.c_str()));
198         co_return;
199     }
200     if (applyTime == ApplyTimeIntf::RequestedApplyTimes::Immediate ||
201         applyTime == ApplyTimeIntf::RequestedApplyTimes::OnReset)
202     {
203         itemUpdater.requestActivation(id);
204     }
205 
206     updateInProgress = false;
207     close(image);
208     co_return;
209 }
210 
211 sdbusplus::message::object_path
212     Manager::startUpdate(sdbusplus::message::unix_fd image,
213                          ApplyTimeIntf::RequestedApplyTimes applyTime)
214 {
215     info("Starting update for image {FD}", "FD", static_cast<int>(image));
216     using sdbusplus::xyz::openbmc_project::Common::Error::Unavailable;
217     if (updateInProgress)
218     {
219         error("Failed to start as update is already in progress");
220         report<Unavailable>();
221         return sdbusplus::message::object_path();
222     }
223     updateInProgress = true;
224 
225     auto id = Version::getId(std::to_string(randomGen()));
226     auto objPath = std::string{SOFTWARE_OBJPATH} + '/' + id;
227 
228     // Create Activation Object
229     itemUpdater.createActivationWithApplyTime(id, objPath, applyTime);
230 
231     int newFd = dup(image);
232     ctx.spawn(processImage(newFd, applyTime, id, objPath));
233 
234     return sdbusplus::message::object_path(objPath);
235 }
236 
237 } // namespace phosphor::software::update
238