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