1 #include "config.h" 2 3 #include "version.hpp" 4 5 #include "xyz/openbmc_project/Common/error.hpp" 6 7 #include <openssl/evp.h> 8 9 #include <phosphor-logging/elog-errors.hpp> 10 #include <phosphor-logging/lg2.hpp> 11 12 #include <fstream> 13 #include <iostream> 14 #include <sstream> 15 #include <stdexcept> 16 #include <string> 17 18 namespace phosphor 19 { 20 namespace software 21 { 22 namespace manager 23 { 24 25 PHOSPHOR_LOG2_USING; 26 using namespace phosphor::logging; 27 using Argument = xyz::openbmc_project::Common::InvalidArgument; 28 using namespace sdbusplus::xyz::openbmc_project::Common::Error; 29 30 std::string Version::getValue(const std::string& manifestFilePath, 31 std::string key) 32 { 33 std::vector<std::string> values = getRepeatedValues(manifestFilePath, key); 34 if (values.empty()) 35 { 36 return std::string{}; 37 } 38 if (values.size() > 1) 39 { 40 error("Multiple values found in MANIFEST file for key: {KEY}", "KEY", 41 key); 42 } 43 return values.at(0); 44 } 45 46 std::vector<std::string> 47 Version::getRepeatedValues(const std::string& manifestFilePath, 48 std::string key) 49 { 50 key = key + "="; 51 auto keySize = key.length(); 52 53 if (manifestFilePath.empty()) 54 { 55 error("ManifestFilePath is empty."); 56 elog<InvalidArgument>( 57 Argument::ARGUMENT_NAME("manifestFilePath"), 58 Argument::ARGUMENT_VALUE(manifestFilePath.c_str())); 59 } 60 61 std::vector<std::string> values{}; 62 std::ifstream efile; 63 std::string line; 64 efile.exceptions(std::ifstream::failbit | std::ifstream::badbit); 65 66 // Too many GCC bugs (53984, 66145) to do this the right way... 67 try 68 { 69 efile.open(manifestFilePath); 70 while (getline(efile, line)) 71 { 72 if (!line.empty() && line.back() == '\r') 73 { 74 // If the manifest has CRLF line terminators, e.g. is created on 75 // Windows, the line will contain \r at the end, remove it. 76 line.pop_back(); 77 } 78 if (line.compare(0, keySize, key) == 0) 79 { 80 values.push_back(line.substr(keySize)); 81 } 82 } 83 efile.close(); 84 } 85 catch (const std::exception& e) 86 { 87 if (!efile.eof()) 88 { 89 error("Error occurred when reading MANIFEST file: {ERROR}", "KEY", 90 key, "ERROR", e); 91 } 92 } 93 94 if (values.empty()) 95 { 96 info("No values found in MANIFEST file for key: {KEY}", "KEY", key); 97 } 98 99 return values; 100 } 101 102 using EVP_MD_CTX_Ptr = 103 std::unique_ptr<EVP_MD_CTX, decltype(&::EVP_MD_CTX_free)>; 104 105 std::string Version::getId(const std::string& version) 106 { 107 108 if (version.empty()) 109 { 110 error("Version is empty."); 111 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Version"), 112 Argument::ARGUMENT_VALUE(version.c_str())); 113 } 114 115 std::array<unsigned char, EVP_MAX_MD_SIZE> digest{}; 116 EVP_MD_CTX_Ptr ctx(EVP_MD_CTX_new(), &::EVP_MD_CTX_free); 117 118 EVP_DigestInit(ctx.get(), EVP_sha512()); 119 EVP_DigestUpdate(ctx.get(), version.c_str(), strlen(version.c_str())); 120 EVP_DigestFinal(ctx.get(), digest.data(), nullptr); 121 122 // We are only using the first 8 characters. 123 char mdString[9]; 124 snprintf(mdString, sizeof(mdString), "%02x%02x%02x%02x", 125 (unsigned int)digest[0], (unsigned int)digest[1], 126 (unsigned int)digest[2], (unsigned int)digest[3]); 127 128 return mdString; 129 } 130 131 std::string Version::getBMCMachine(const std::string& releaseFilePath) 132 { 133 std::string machineKey = "OPENBMC_TARGET_MACHINE="; 134 std::string machine{}; 135 std::ifstream efile(releaseFilePath); 136 std::string line; 137 138 while (getline(efile, line)) 139 { 140 if (line.substr(0, machineKey.size()).find(machineKey) != 141 std::string::npos) 142 { 143 std::size_t pos = line.find_first_of('"') + 1; 144 machine = line.substr(pos, line.find_last_of('"') - pos); 145 break; 146 } 147 } 148 149 if (machine.empty()) 150 { 151 error("Unable to find OPENBMC_TARGET_MACHINE"); 152 elog<InternalFailure>(); 153 } 154 155 return machine; 156 } 157 158 std::string Version::getBMCExtendedVersion(const std::string& releaseFilePath) 159 { 160 std::string extendedVersionKey = "EXTENDED_VERSION="; 161 std::string extendedVersionValue{}; 162 std::string extendedVersion{}; 163 std::ifstream efile(releaseFilePath); 164 std::string line; 165 166 while (getline(efile, line)) 167 { 168 if (line.substr(0, extendedVersionKey.size()) 169 .find(extendedVersionKey) != std::string::npos) 170 { 171 extendedVersionValue = line.substr(extendedVersionKey.size()); 172 std::size_t pos = extendedVersionValue.find_first_of('"') + 1; 173 extendedVersion = extendedVersionValue.substr( 174 pos, extendedVersionValue.find_last_of('"') - pos); 175 break; 176 } 177 } 178 179 return extendedVersion; 180 } 181 182 std::string Version::getBMCVersion(const std::string& releaseFilePath) 183 { 184 std::string versionKey = "VERSION_ID="; 185 std::string versionValue{}; 186 std::string version{}; 187 std::ifstream efile; 188 std::string line; 189 efile.open(releaseFilePath); 190 191 while (getline(efile, line)) 192 { 193 if (line.substr(0, versionKey.size()).find(versionKey) != 194 std::string::npos) 195 { 196 // Support quoted and unquoted values 197 // 1. Remove the versionKey so that we process the value only. 198 versionValue = line.substr(versionKey.size()); 199 200 // 2. Look for a starting quote, then increment the position by 1 to 201 // skip the quote character. If no quote is found, 202 // find_first_of() returns npos (-1), which by adding +1 sets pos 203 // to 0 (beginning of unquoted string). 204 std::size_t pos = versionValue.find_first_of('"') + 1; 205 206 // 3. Look for ending quote, then decrease the position by pos to 207 // get the size of the string up to before the ending quote. If 208 // no quote is found, find_last_of() returns npos (-1), and pos 209 // is 0 for the unquoted case, so substr() is called with a len 210 // parameter of npos (-1) which according to the documentation 211 // indicates to use all characters until the end of the string. 212 version = 213 versionValue.substr(pos, versionValue.find_last_of('"') - pos); 214 break; 215 } 216 } 217 efile.close(); 218 219 if (version.empty()) 220 { 221 error("BMC current version is empty"); 222 elog<InternalFailure>(); 223 } 224 225 return version; 226 } 227 228 void Delete::delete_() 229 { 230 if (parent.eraseCallback) 231 { 232 parent.eraseCallback(parent.id); 233 } 234 } 235 236 } // namespace manager 237 } // namespace software 238 } // namespace phosphor 239