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 namespace fs = std::filesystem;
38 
39 struct RemovablePath
40 {
41     fs::path path;
42 
43     RemovablePath(const fs::path& path) : path(path)
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         fs::remove_all(imageDirPath);
215     }
216     return 0;
217 }
218 
219 void Manager::erase(std::string entryId)
220 {
221     auto it = versions.find(entryId);
222     if (it == versions.end())
223     {
224         return;
225     }
226 
227     if (it->second->isFunctional())
228     {
229         log<level::ERR>(("Error: Version " + entryId +
230                          " is currently running on the BMC."
231                          " Unable to remove.")
232                             .c_str());
233         return;
234     }
235 
236     // Delete image dir
237     fs::path imageDirPath = (*(it->second)).path();
238     if (fs::exists(imageDirPath))
239     {
240         fs::remove_all(imageDirPath);
241     }
242     this->versions.erase(entryId);
243 }
244 
245 int Manager::unTar(const std::string& tarFilePath,
246                    const std::string& extractDirPath)
247 {
248     if (tarFilePath.empty())
249     {
250         log<level::ERR>("Error TarFilePath is empty");
251         report<UnTarFailure>(UnTarFail::PATH(tarFilePath.c_str()));
252         return -1;
253     }
254     if (extractDirPath.empty())
255     {
256         log<level::ERR>("Error ExtractDirPath is empty");
257         report<UnTarFailure>(UnTarFail::PATH(extractDirPath.c_str()));
258         return -1;
259     }
260 
261     log<level::INFO>("Untaring", entry("FILENAME=%s", tarFilePath.c_str()),
262                      entry("EXTRACTIONDIR=%s", extractDirPath.c_str()));
263     int status = 0;
264     pid_t pid = fork();
265 
266     if (pid == 0)
267     {
268         // child process
269         execl("/bin/tar", "tar", "-xf", tarFilePath.c_str(), "-C",
270               extractDirPath.c_str(), (char*)0);
271         // execl only returns on fail
272         log<level::ERR>("Failed to execute untar file",
273                         entry("FILENAME=%s", tarFilePath.c_str()));
274         report<UnTarFailure>(UnTarFail::PATH(tarFilePath.c_str()));
275         return -1;
276     }
277     else if (pid > 0)
278     {
279         waitpid(pid, &status, 0);
280         if (WEXITSTATUS(status))
281         {
282             log<level::ERR>("Failed to untar file",
283                             entry("FILENAME=%s", tarFilePath.c_str()));
284             report<UnTarFailure>(UnTarFail::PATH(tarFilePath.c_str()));
285             return -1;
286         }
287     }
288     else
289     {
290         log<level::ERR>("fork() failed.");
291         report<UnTarFailure>(UnTarFail::PATH(tarFilePath.c_str()));
292         return -1;
293     }
294 
295     return 0;
296 }
297 
298 } // namespace manager
299 } // namespace software
300 } // namespace phosphor
301