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