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