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 <iterator>
10 #include <regex>
11 
12 namespace openpower
13 {
14 namespace software
15 {
16 namespace image
17 {
18 
19 using namespace phosphor::logging;
20 using AssociationList =
21     std::vector<std::tuple<std::string, std::string, std::string>>;
22 
compare(const Version & a,const Version & b)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 
parse(const std::string & versionStr,Version & version)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 
getFunctionalVersion()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     std::variant<AssociationList> associations;
92     try
93     {
94         response.read(associations);
95     }
96     catch (const sdbusplus::exception_t& 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 = std::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             std::variant<std::string> functionalVersion;
121             try
122             {
123                 response.read(functionalVersion);
124                 return std::get<std::string>(functionalVersion);
125             }
126             catch (const sdbusplus::exception_t& 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 
verify()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 (const auto& 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 Minimum 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