1 #include "config.h" 2 3 #include "msl_verify.hpp" 4 5 #include <phosphor-logging/log.hpp> 6 7 #include <filesystem> 8 #include <fstream> 9 #include <regex> 10 11 namespace openpower 12 { 13 namespace software 14 { 15 namespace image 16 { 17 18 using namespace phosphor::logging; 19 using AssociationList = 20 std::vector<std::tuple<std::string, std::string, std::string>>; 21 22 int MinimumShipLevel::compare(const Version& a, const Version& b) 23 { 24 if (a.major < b.major) 25 { 26 return -1; 27 } 28 else if (a.major > b.major) 29 { 30 return 1; 31 } 32 33 if (a.minor < b.minor) 34 { 35 return -1; 36 } 37 else if (a.minor > b.minor) 38 { 39 return 1; 40 } 41 42 if (a.rev < b.rev) 43 { 44 return -1; 45 } 46 else if (a.rev > b.rev) 47 { 48 return 1; 49 } 50 51 return 0; 52 } 53 54 void MinimumShipLevel::parse(const std::string& versionStr, Version& version) 55 { 56 std::smatch match; 57 version = {0, 0, 0}; 58 59 // Match for vX.Y.Z or v-X.Y.Z 60 std::regex regex{"v-?([0-9]+)\\.([0-9]+)\\.([0-9]+)", std::regex::extended}; 61 62 if (!std::regex_search(versionStr, match, regex)) 63 { 64 // Match for vX.Y or v-X.Y 65 std::regex regexShort{"v-?([0-9]+)\\.([0-9]+)", std::regex::extended}; 66 if (!std::regex_search(versionStr, match, regexShort)) 67 { 68 log<level::ERR>("Unable to parse PNOR version", 69 entry("VERSION=%s", versionStr.c_str())); 70 return; 71 } 72 } 73 else 74 { 75 // Populate Z 76 version.rev = std::stoi(match[3]); 77 } 78 version.major = std::stoi(match[1]); 79 version.minor = std::stoi(match[2]); 80 } 81 82 std::string MinimumShipLevel::getFunctionalVersion() 83 { 84 auto bus = sdbusplus::bus::new_default(); 85 auto method = bus.new_method_call(BUSNAME_UPDATER, SOFTWARE_OBJPATH, 86 SYSTEMD_PROPERTY_INTERFACE, "Get"); 87 method.append(ASSOCIATIONS_INTERFACE, "Associations"); 88 auto response = bus.call(method); 89 90 std::variant<AssociationList> associations; 91 try 92 { 93 response.read(associations); 94 } 95 catch (const sdbusplus::exception::exception& e) 96 { 97 log<level::ERR>("Failed to read software associations", 98 entry("ERROR=%s", e.what()), 99 entry("SIGNATURE=%s", response.get_signature())); 100 return {}; 101 } 102 103 auto& assocs = std::get<AssociationList>(associations); 104 if (assocs.empty()) 105 { 106 return {}; 107 } 108 109 for (const auto& assoc : assocs) 110 { 111 if (std::get<0>(assoc).compare(FUNCTIONAL_FWD_ASSOCIATION) == 0) 112 { 113 auto path = std::get<2>(assoc); 114 method = bus.new_method_call(BUSNAME_UPDATER, path.c_str(), 115 SYSTEMD_PROPERTY_INTERFACE, "Get"); 116 method.append(VERSION_IFACE, "Version"); 117 response = bus.call(method); 118 119 std::variant<std::string> functionalVersion; 120 try 121 { 122 response.read(functionalVersion); 123 return std::get<std::string>(functionalVersion); 124 } 125 catch (const sdbusplus::exception::exception& e) 126 { 127 log<level::ERR>( 128 "Failed to read version property", 129 entry("ERROR=%s", e.what()), 130 entry("SIGNATURE=%s", response.get_signature())); 131 return {}; 132 } 133 } 134 } 135 136 return {}; 137 } 138 139 bool MinimumShipLevel::verify() 140 { 141 if (minShipLevel.empty()) 142 { 143 return true; 144 } 145 146 auto actual = getFunctionalVersion(); 147 if (actual.empty()) 148 { 149 return true; 150 } 151 152 // Multiple min versions separated by a space can be specified, parse them 153 // into a vector, then sort them in ascending order 154 std::istringstream minStream(minShipLevel); 155 std::vector<std::string> mins(std::istream_iterator<std::string>{minStream}, 156 std::istream_iterator<std::string>()); 157 std::sort(mins.begin(), mins.end()); 158 159 // In order to handle non-continuous multiple min versions, need to compare 160 // the major.minor section first, then if they're the same, compare the rev. 161 // Ex: the min versions specified are 2.0.10 and 2.2. We need to pass if 162 // actual is 2.0.11 but fail if it's 2.1.x. 163 // 1. Save off the rev number to compare later if needed. 164 // 2. Zero out the rev number to just compare major and minor. 165 Version actualVersion = {0, 0, 0}; 166 parse(actual, actualVersion); 167 Version actualRev = {0, 0, actualVersion.rev}; 168 actualVersion.rev = 0; 169 170 auto rc = 0; 171 std::string tmpMin{}; 172 173 for (auto const& min : mins) 174 { 175 tmpMin = min; 176 177 Version minVersion = {0, 0, 0}; 178 parse(min, minVersion); 179 Version minRev = {0, 0, minVersion.rev}; 180 minVersion.rev = 0; 181 182 rc = compare(actualVersion, minVersion); 183 if (rc < 0) 184 { 185 break; 186 } 187 else if (rc == 0) 188 { 189 // Same major.minor version, compare the rev 190 rc = compare(actualRev, minRev); 191 break; 192 } 193 } 194 if (rc < 0) 195 { 196 log<level::ERR>( 197 "PNOR Mininum Ship Level NOT met", 198 entry("MIN_VERSION=%s", tmpMin.c_str()), 199 entry("ACTUAL_VERSION=%s", actual.c_str()), 200 entry("VERSION_PURPOSE=%s", 201 "xyz.openbmc_project.Software.Version.VersionPurpose.Host")); 202 return false; 203 } 204 205 return true; 206 } 207 208 } // namespace image 209 } // namespace software 210 } // namespace openpower 211