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