1 #include "config.h"
2
3 #include "utils.hpp"
4
5 #include <openssl/evp.h>
6
7 #include <phosphor-logging/lg2.hpp>
8
9 #include <algorithm>
10 #include <cerrno>
11 #include <cstring>
12 #include <exception>
13 #include <format>
14 #include <fstream>
15 #include <sstream>
16 #include <stdexcept>
17
18 namespace utils
19 {
20
21 namespace // anonymous
22 {
23 constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper";
24 constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper";
25 constexpr auto MAPPER_INTERFACE = "xyz.openbmc_project.ObjectMapper";
26 } // namespace
27
28 namespace internal
29 {
30
31 /**
32 * @brief Concatenate the specified values, separated by spaces, and return
33 * the resulting string.
34 *
35 * @param[in] ts - Parameter pack of values to concatenate
36 *
37 * @return Parameter values separated by spaces
38 */
39 template <typename... Ts>
concat_string(const Ts &...ts)40 std::string concat_string(const Ts&... ts)
41 {
42 std::stringstream s;
43 ((s << ts << " "), ...);
44 return s.str();
45 }
46
47 /**
48 * @brief Execute the specified command.
49 *
50 * @details Returns a pair containing the exit status and command output.
51 * Throws an exception if an error occurs. Note that a command that
52 * returns a non-zero exit status is not considered an error.
53 *
54 * @param[in] ts - Parameter pack of command and parameters
55 *
56 * @return Exit status and standard output from the command
57 */
58 template <typename... Ts>
exec(const Ts &...ts)59 std::pair<int, std::string> exec(const Ts&... ts)
60 {
61 std::array<char, 512> buffer;
62 std::string cmd = concat_string(ts...);
63 std::stringstream result;
64 int rc;
65 FILE* pipe = popen(cmd.c_str(), "r");
66 if (!pipe)
67 {
68 throw std::runtime_error{
69 std::format("Unable to execute command '{}': popen() failed: {}",
70 cmd, std::strerror(errno))};
71 }
72 while (fgets(buffer.data(), buffer.size(), pipe) != nullptr)
73 {
74 result << buffer.data();
75 }
76 rc = pclose(pipe);
77 return {rc, result.str()};
78 }
79
80 } // namespace internal
81
getUtils()82 const UtilsInterface& getUtils()
83 {
84 static Utils utils;
85 return utils;
86 }
87
getPSUInventoryPaths(sdbusplus::bus_t & bus) const88 std::vector<std::string> Utils::getPSUInventoryPaths(
89 sdbusplus::bus_t& bus) const
90 {
91 std::vector<std::string> paths;
92 try
93 {
94 auto method = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
95 MAPPER_INTERFACE, "GetSubTreePaths");
96 method.append(PSU_INVENTORY_PATH_BASE);
97 method.append(0); // Depth 0 to search all
98 method.append(std::vector<std::string>({PSU_INVENTORY_IFACE}));
99 auto reply = bus.call(method);
100
101 reply.read(paths);
102 }
103 catch (const std::exception& e)
104 {
105 // Inventory base path not there yet.
106 }
107 return paths;
108 }
109
getService(sdbusplus::bus_t & bus,const char * path,const char * interface) const110 std::string Utils::getService(sdbusplus::bus_t& bus, const char* path,
111 const char* interface) const
112 {
113 auto services = getServices(bus, path, interface);
114 if (services.empty())
115 {
116 throw std::runtime_error{std::format(
117 "No service found for path {}, interface {}", path, interface)};
118 }
119 return services[0];
120 }
121
getServices(sdbusplus::bus_t & bus,const char * path,const char * interface) const122 std::vector<std::string> Utils::getServices(
123 sdbusplus::bus_t& bus, const char* path, const char* interface) const
124 {
125 std::vector<std::string> services;
126 try
127 {
128 auto mapper = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
129 MAPPER_INTERFACE, "GetObject");
130
131 mapper.append(path, std::vector<std::string>({interface}));
132
133 auto mapperResponseMsg = bus.call(mapper);
134
135 auto mapperResponse = mapperResponseMsg.unpack<
136 std::vector<std::pair<std::string, std::vector<std::string>>>>();
137 services.reserve(mapperResponse.size());
138 for (const auto& i : mapperResponse)
139 {
140 services.emplace_back(i.first);
141 }
142 }
143 catch (const std::exception& e)
144 {
145 throw std::runtime_error{
146 std::format("Unable to find services for path {}, interface {}: {}",
147 path, interface, e.what())};
148 }
149 return services;
150 }
151
getVersionId(const std::string & version) const152 std::string Utils::getVersionId(const std::string& version) const
153 {
154 if (version.empty())
155 {
156 lg2::error("Error version is empty");
157 return {};
158 }
159
160 using EVP_MD_CTX_Ptr =
161 std::unique_ptr<EVP_MD_CTX, decltype(&::EVP_MD_CTX_free)>;
162
163 std::array<unsigned char, EVP_MAX_MD_SIZE> digest{};
164 EVP_MD_CTX_Ptr ctx(EVP_MD_CTX_new(), &::EVP_MD_CTX_free);
165
166 EVP_DigestInit(ctx.get(), EVP_sha512());
167 EVP_DigestUpdate(ctx.get(), version.c_str(), strlen(version.c_str()));
168 EVP_DigestFinal(ctx.get(), digest.data(), nullptr);
169
170 // Only need 8 hex digits.
171 char mdString[9];
172 snprintf(mdString, sizeof(mdString), "%02x%02x%02x%02x",
173 (unsigned int)digest[0], (unsigned int)digest[1],
174 (unsigned int)digest[2], (unsigned int)digest[3]);
175
176 return mdString;
177 }
178
getVersion(const std::string & inventoryPath) const179 std::string Utils::getVersion(const std::string& inventoryPath) const
180 {
181 std::string version;
182 try
183 {
184 // Invoke vendor-specific tool to get the version string, e.g.
185 // psutils --get-version
186 // /xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply0
187 auto [rc, output] = internal::exec(PSU_VERSION_UTIL, inventoryPath);
188 if (rc == 0)
189 {
190 version = output;
191 }
192 }
193 catch (const std::exception& e)
194 {
195 lg2::error("Unable to get firmware version for PSU {PSU}: {ERROR}",
196 "PSU", inventoryPath, "ERROR", e);
197 }
198 return version;
199 }
200
getModel(const std::string & inventoryPath) const201 std::string Utils::getModel(const std::string& inventoryPath) const
202 {
203 std::string model;
204 try
205 {
206 // Invoke vendor-specific tool to get the model string, e.g.
207 // psutils --get-model
208 // /xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply0
209 auto [rc, output] = internal::exec(PSU_MODEL_UTIL, inventoryPath);
210 if (rc == 0)
211 {
212 model = output;
213 }
214 }
215 catch (const std::exception& e)
216 {
217 lg2::error("Unable to get model for PSU {PSU}: {ERROR}", "PSU",
218 inventoryPath, "ERROR", e);
219 }
220 return model;
221 }
222
getLatestVersion(const std::set<std::string> & versions) const223 std::string Utils::getLatestVersion(const std::set<std::string>& versions) const
224 {
225 std::string latestVersion;
226 try
227 {
228 if (!versions.empty())
229 {
230 std::stringstream args;
231 for (const auto& s : versions)
232 {
233 args << s << " ";
234 }
235 auto [rc, output] =
236 internal::exec(PSU_VERSION_COMPARE_UTIL, args.str());
237 if (rc == 0)
238 {
239 latestVersion = output;
240 }
241 }
242 }
243 catch (const std::exception& e)
244 {
245 lg2::error("Unable to get latest PSU firmware version: {ERROR}",
246 "ERROR", e);
247 }
248 return latestVersion;
249 }
250
isAssociated(const std::string & psuInventoryPath,const AssociationList & assocs) const251 bool Utils::isAssociated(const std::string& psuInventoryPath,
252 const AssociationList& assocs) const
253 {
254 return std::find_if(assocs.begin(), assocs.end(),
255 [&psuInventoryPath](const auto& assoc) {
256 return psuInventoryPath == std::get<2>(assoc);
257 }) != assocs.end();
258 }
259
getPropertyImpl(sdbusplus::bus_t & bus,const char * service,const char * path,const char * interface,const char * propertyName) const260 any Utils::getPropertyImpl(sdbusplus::bus_t& bus, const char* service,
261 const char* path, const char* interface,
262 const char* propertyName) const
263 {
264 any anyValue{};
265 try
266 {
267 auto method = bus.new_method_call(
268 service, path, "org.freedesktop.DBus.Properties", "Get");
269 method.append(interface, propertyName);
270 auto reply = bus.call(method);
271 PropertyType value{};
272 reply.read(value);
273 anyValue = value;
274 }
275 catch (const std::exception& e)
276 {
277 throw std::runtime_error{std::format(
278 "Unable to get property {} for path {} and interface {}: {}",
279 propertyName, path, interface, e.what())};
280 }
281 return anyValue;
282 }
283
284 } // namespace utils
285