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