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