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