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