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