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
processImageFailed(sdbusplus::message::unix_fd image,std::string & id)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
verifyImagePurpose(Version::VersionPurpose purpose,ItemUpdaterIntf::UpdaterType type)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)
processImage(sdbusplus::message::unix_fd image,ApplyTimeIntf::RequestedApplyTimes applyTime,std::string id,std::string objPath)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
startUpdate(sdbusplus::message::unix_fd image,ApplyTimeIntf::RequestedApplyTimes applyTime)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