xref: /openbmc/phosphor-bmc-code-mgmt/image_manager.cpp (revision fc33ba86773d2f02cee1093bea3e5da405028220)
1 #include "config.h"
2 
3 #include "image_manager.hpp"
4 
5 #include "version.hpp"
6 #include "watch.hpp"
7 
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <sys/stat.h>
11 #include <sys/wait.h>
12 #include <unistd.h>
13 
14 #include <phosphor-logging/elog-errors.hpp>
15 #include <phosphor-logging/elog.hpp>
16 #include <phosphor-logging/lg2.hpp>
17 #include <xyz/openbmc_project/Software/Image/error.hpp>
18 
19 #include <algorithm>
20 #include <cstring>
21 #include <ctime>
22 #include <filesystem>
23 #include <random>
24 #include <string>
25 #include <system_error>
26 
27 namespace phosphor
28 {
29 namespace software
30 {
31 namespace manager
32 {
33 
34 PHOSPHOR_LOG2_USING;
35 using namespace phosphor::logging;
36 using namespace sdbusplus::error::xyz::openbmc_project::software::image;
37 namespace Software = phosphor::logging::xyz::openbmc_project::software;
38 using ManifestFail = Software::image::ManifestFileFailure;
39 using UnTarFail = Software::image::UnTarFailure;
40 using InternalFail = Software::image::InternalFailure;
41 using ImageFail = Software::image::ImageFailure;
42 namespace fs = std::filesystem;
43 
44 struct RemovablePath
45 {
46     fs::path path;
47 
RemovablePathphosphor::software::manager::RemovablePath48     explicit RemovablePath(const fs::path& path) : path(path) {}
~RemovablePathphosphor::software::manager::RemovablePath49     ~RemovablePath()
50     {
51         if (!path.empty())
52         {
53             std::error_code ec;
54             fs::remove_all(path, ec);
55         }
56     }
57 
58     RemovablePath(const RemovablePath& other) = delete;
59     RemovablePath& operator=(const RemovablePath& other) = delete;
60     RemovablePath(RemovablePath&&) = delete;
61     RemovablePath& operator=(RemovablePath&&) = delete;
62 };
63 
64 namespace // anonymous
65 {
66 
getSoftwareObjects(sdbusplus::bus_t & bus)67 std::vector<std::string> getSoftwareObjects(sdbusplus::bus_t& bus)
68 {
69     std::vector<std::string> paths;
70     auto method = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
71                                       MAPPER_INTERFACE, "GetSubTreePaths");
72     method.append(SOFTWARE_OBJPATH);
73     method.append(0); // Depth 0 to search all
74     method.append(std::vector<std::string>({VERSION_BUSNAME}));
75     auto reply = bus.call(method);
76     reply.read(paths);
77     return paths;
78 }
79 
80 } // namespace
81 
processImage(const std::string & tarFilePath)82 int Manager::processImage(const std::string& tarFilePath)
83 {
84     std::error_code ec;
85     if (!fs::is_regular_file(tarFilePath, ec))
86     {
87         error("Tarball {PATH} does not exist: {ERROR_MSG}", "PATH", tarFilePath,
88               "ERROR_MSG", ec.message());
89         report<ManifestFileFailure>(ManifestFail::PATH(tarFilePath.c_str()));
90         return -1;
91     }
92     RemovablePath tarPathRemove(tarFilePath);
93     fs::path tmpDirPath(std::string{IMG_UPLOAD_DIR});
94     tmpDirPath /= "imageXXXXXX";
95     auto tmpDir = tmpDirPath.string();
96 
97     // Create a tmp dir to extract tarball.
98     if (!mkdtemp(tmpDir.data()))
99     {
100         error("Error ({ERRNO}) occurred during mkdtemp", "ERRNO", errno);
101         report<InternalFailure>(InternalFail::FAIL("mkdtemp"));
102         return -1;
103     }
104 
105     tmpDirPath = tmpDir;
106     RemovablePath tmpDirToRemove(tmpDirPath);
107     fs::path manifestPath = tmpDirPath;
108     manifestPath /= MANIFEST_FILE_NAME;
109 
110     // Untar tarball into the tmp dir
111     auto rc = unTar(tarFilePath, tmpDirPath.string());
112     if (rc < 0)
113     {
114         error("Error ({RC}) occurred during untar", "RC", rc);
115         return -1;
116     }
117 
118     // Verify the manifest file
119     if (!fs::is_regular_file(manifestPath, ec))
120     {
121         error("No manifest file {PATH}: {ERROR_MSG}", "PATH", tarFilePath,
122               "ERROR_MSG", ec.message());
123         report<ManifestFileFailure>(ManifestFail::PATH(tarFilePath.c_str()));
124         return -1;
125     }
126 
127     // Get version
128     auto version = Version::getValue(manifestPath.string(), "version");
129     if (version.empty())
130     {
131         error("Unable to read version from manifest file {PATH}", "PATH",
132               tarFilePath);
133         report<ManifestFileFailure>(ManifestFail::PATH(tarFilePath.c_str()));
134         return -1;
135     }
136 
137     // Get running machine name
138     std::string currMachine = Version::getBMCMachine(OS_RELEASE_FILE);
139     if (currMachine.empty())
140     {
141         auto path = OS_RELEASE_FILE;
142         error("Failed to read machine name from osRelease: {PATH}", "PATH",
143               path);
144         report<ImageFailure>(ImageFail::FAIL("Failed to read machine name"),
145                              ImageFail::PATH(path));
146         return -1;
147     }
148 
149     // Get machine name for image to be upgraded
150     std::string machineStr =
151         Version::getValue(manifestPath.string(), "MachineName");
152     if (!machineStr.empty())
153     {
154         if (machineStr != currMachine)
155         {
156             error(
157                 "BMC upgrade: Machine name doesn't match: {CURRENT_MACHINE} vs {NEW_MACHINE}",
158                 "CURRENT_MACHINE", currMachine, "NEW_MACHINE", machineStr);
159             report<ImageFailure>(
160                 ImageFail::FAIL("Machine name does not match"),
161                 ImageFail::PATH(manifestPath.string().c_str()));
162             return -1;
163         }
164     }
165     else
166     {
167         warning("No machine name in Manifest file");
168         report<ImageFailure>(
169             ImageFail::FAIL("MANIFEST is missing machine name"),
170             ImageFail::PATH(manifestPath.string().c_str()));
171     }
172 
173     // Get purpose
174     auto purposeString = Version::getValue(manifestPath.string(), "purpose");
175     if (purposeString.empty())
176     {
177         error("Unable to read purpose from manifest file {PATH}", "PATH",
178               tarFilePath);
179         report<ManifestFileFailure>(ManifestFail::PATH(tarFilePath.c_str()));
180         return -1;
181     }
182 
183     auto convertedPurpose =
184         sdbusplus::message::convert_from_string<Version::VersionPurpose>(
185             purposeString);
186 
187     if (!convertedPurpose)
188     {
189         error(
190             "Failed to convert manifest purpose ({PURPOSE}) to enum; setting to Unknown.",
191             "PURPOSE", purposeString);
192     }
193     auto purpose = convertedPurpose.value_or(Version::VersionPurpose::Unknown);
194 
195     // Get ExtendedVersion
196     std::string extendedVersion =
197         Version::getValue(manifestPath.string(), "ExtendedVersion");
198 
199     // Get CompatibleNames
200     std::vector<std::string> compatibleNames =
201         Version::getRepeatedValues(manifestPath.string(), "CompatibleName");
202 
203     // Compute id
204     auto salt = std::to_string(randomGen());
205     auto id = Version::getId(version + salt);
206 
207     fs::path imageDirPath = std::string{IMG_UPLOAD_DIR};
208     imageDirPath /= id;
209 
210     auto objPath = std::string{SOFTWARE_OBJPATH} + '/' + id;
211 
212     // This service only manages the uploaded versions, and there could be
213     // active versions on D-Bus that is not managed by this service.
214     // So check D-Bus if there is an existing version.
215     auto allSoftwareObjs = getSoftwareObjects(bus);
216     auto it =
217         std::find(allSoftwareObjs.begin(), allSoftwareObjs.end(), objPath);
218     if (versions.find(id) == versions.end() && it == allSoftwareObjs.end())
219     {
220         // Rename the temp dir to image dir
221         fs::rename(tmpDirPath, imageDirPath, ec);
222         // Clear the path, so it does not attempt to remove a non-existing path
223         tmpDirToRemove.path.clear();
224 
225         // Create Version object
226         auto versionPtr = std::make_unique<Version>(
227             bus, objPath, version, purpose, extendedVersion,
228             imageDirPath.string(), compatibleNames,
229             std::bind(&Manager::erase, this, std::placeholders::_1), id);
230         versionPtr->deleteObject =
231             std::make_unique<phosphor::software::manager::Delete>(
232                 bus, objPath, *versionPtr);
233         versions.insert(std::make_pair(id, std::move(versionPtr)));
234     }
235     else
236     {
237         info("Software Object with the same version ({VERSION}) already exists",
238              "VERSION", id);
239     }
240     return 0;
241 }
242 
erase(const std::string & entryId)243 void Manager::erase(const std::string& entryId)
244 {
245     auto it = versions.find(entryId);
246     if (it == versions.end())
247     {
248         return;
249     }
250 
251     // Delete image dir
252     fs::path imageDirPath = (*(it->second)).path();
253     std::error_code ec;
254     if (fs::exists(imageDirPath, ec))
255     {
256         fs::remove_all(imageDirPath, ec);
257     }
258     this->versions.erase(entryId);
259 }
260 
unTar(const std::string & tarFilePath,const std::string & extractDirPath)261 int Manager::unTar(const std::string& tarFilePath,
262                    const std::string& extractDirPath)
263 {
264     if (tarFilePath.empty())
265     {
266         error("TarFilePath is empty");
267         report<UnTarFailure>(UnTarFail::PATH(tarFilePath.c_str()));
268         return -1;
269     }
270     if (extractDirPath.empty())
271     {
272         error("ExtractDirPath is empty");
273         report<UnTarFailure>(UnTarFail::PATH(extractDirPath.c_str()));
274         return -1;
275     }
276 
277     info("Untaring {PATH} to {EXTRACTIONDIR}", "PATH", tarFilePath,
278          "EXTRACTIONDIR", extractDirPath);
279     int status = 0;
280     pid_t pid = fork();
281 
282     if (pid == 0)
283     {
284         // child process
285         execl("/bin/tar", "tar", "-xf", tarFilePath.c_str(), "-C",
286               extractDirPath.c_str(), (char*)0);
287         // execl only returns on fail
288         error("Failed to execute untar on {PATH}", "PATH", tarFilePath);
289         report<UnTarFailure>(UnTarFail::PATH(tarFilePath.c_str()));
290         return -1;
291     }
292     else if (pid > 0)
293     {
294         waitpid(pid, &status, 0);
295         if (WEXITSTATUS(status))
296         {
297             error("Failed ({STATUS}) to untar file {PATH}", "STATUS", status,
298                   "PATH", tarFilePath);
299             report<UnTarFailure>(UnTarFail::PATH(tarFilePath.c_str()));
300             return -1;
301         }
302     }
303     else
304     {
305         error("fork() failed: {ERRNO}", "ERRNO", errno);
306         report<UnTarFailure>(UnTarFail::PATH(tarFilePath.c_str()));
307         return -1;
308     }
309 
310     return 0;
311 }
312 
313 } // namespace manager
314 } // namespace software
315 } // namespace phosphor
316