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