blob: 27b6eea8e7a0ee848f235478cb80aaad781d5d64 [file] [log] [blame]
Lei YU5e0dcb32019-08-02 18:04:34 +08001#include "config.h"
2
3#include "utils.hpp"
4
George Liu0b7c7b32022-02-07 16:30:23 +08005#include <openssl/evp.h>
Lei YUad90ad52019-08-06 11:19:28 +08006
Shawn McCarneycdf86de2024-11-26 10:02:14 -06007#include <phosphor-logging/lg2.hpp>
Patrick Williams5670b182023-05-10 07:50:50 -05008
Lei YU4b9ac392019-10-12 11:02:26 +08009#include <algorithm>
Shawn McCarney487e2e12024-11-25 17:19:46 -060010#include <cerrno>
11#include <cstring>
12#include <exception>
13#include <format>
Lei YU5e0dcb32019-08-02 18:04:34 +080014#include <fstream>
Lei YU5f3584d2019-08-27 16:28:53 +080015#include <sstream>
Shawn McCarney487e2e12024-11-25 17:19:46 -060016#include <stdexcept>
Lei YUf77189f2019-08-07 14:26:30 +080017
Lei YU5e0dcb32019-08-02 18:04:34 +080018namespace utils
19{
20
21namespace // anonymous
22{
23constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper";
24constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper";
25constexpr auto MAPPER_INTERFACE = "xyz.openbmc_project.ObjectMapper";
26} // namespace
27
Lei YU5f3584d2019-08-27 16:28:53 +080028namespace internal
29{
Shawn McCarney487e2e12024-11-25 17:19:46 -060030
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 */
Lei YU5f3584d2019-08-27 16:28:53 +080039template <typename... Ts>
Patrick Williams5670b182023-05-10 07:50:50 -050040std::string concat_string(const Ts&... ts)
Lei YU5f3584d2019-08-27 16:28:53 +080041{
42 std::stringstream s;
Shawn McCarney487e2e12024-11-25 17:19:46 -060043 ((s << ts << " "), ...);
Lei YU5f3584d2019-08-27 16:28:53 +080044 return s.str();
45}
46
Shawn McCarney487e2e12024-11-25 17:19:46 -060047/**
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 */
Lei YU5f3584d2019-08-27 16:28:53 +080058template <typename... Ts>
Patrick Williams5670b182023-05-10 07:50:50 -050059std::pair<int, std::string> exec(const Ts&... ts)
Lei YU5f3584d2019-08-27 16:28:53 +080060{
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 {
Shawn McCarney487e2e12024-11-25 17:19:46 -060068 throw std::runtime_error{
69 std::format("Unable to execute command '{}': popen() failed: {}",
70 cmd, std::strerror(errno))};
Lei YU5f3584d2019-08-27 16:28:53 +080071 }
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
Shawn McCarney487e2e12024-11-25 17:19:46 -060081
Lei YUf77189f2019-08-07 14:26:30 +080082const UtilsInterface& getUtils()
83{
84 static Utils utils;
85 return utils;
86}
87
Shawn McCarneyd57bd2f2024-12-02 18:40:28 -060088std::vector<std::string>
89 Utils::getPSUInventoryPaths(sdbusplus::bus_t& bus) const
Lei YU5e0dcb32019-08-02 18:04:34 +080090{
91 std::vector<std::string> paths;
Matt Spinlere183edc2024-07-09 11:25:34 -050092 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);
Lei YU5e0dcb32019-08-02 18:04:34 +0800100
Matt Spinlere183edc2024-07-09 11:25:34 -0500101 reply.read(paths);
102 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600103 catch (const std::exception& e)
Matt Spinlere183edc2024-07-09 11:25:34 -0500104 {
105 // Inventory base path not there yet.
106 }
Lei YU5e0dcb32019-08-02 18:04:34 +0800107 return paths;
108}
109
Patrick Williams374fae52022-07-22 19:26:55 -0500110std::string Utils::getService(sdbusplus::bus_t& bus, const char* path,
Lei YUf77189f2019-08-07 14:26:30 +0800111 const char* interface) const
Lei YUad90ad52019-08-06 11:19:28 +0800112{
Lei YUd0bbfa92019-09-11 16:10:54 +0800113 auto services = getServices(bus, path, interface);
114 if (services.empty())
115 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600116 throw std::runtime_error{std::format(
117 "No service found for path {}, interface {}", path, interface)};
Lei YUd0bbfa92019-09-11 16:10:54 +0800118 }
119 return services[0];
120}
121
Patrick Williamsbab5ed92024-08-16 15:20:54 -0400122std::vector<std::string> Utils::getServices(
123 sdbusplus::bus_t& bus, const char* path, const char* interface) const
Lei YUd0bbfa92019-09-11 16:10:54 +0800124{
Shawn McCarney487e2e12024-11-25 17:19:46 -0600125 std::vector<std::string> services;
Lei YUad90ad52019-08-06 11:19:28 +0800126 try
127 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600128 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
Lei YUad90ad52019-08-06 11:19:28 +0800133 auto mapperResponseMsg = bus.call(mapper);
134
135 std::vector<std::pair<std::string, std::vector<std::string>>>
136 mapperResponse;
137 mapperResponseMsg.read(mapperResponse);
Shawn McCarney487e2e12024-11-25 17:19:46 -0600138 services.reserve(mapperResponse.size());
Lei YUd0bbfa92019-09-11 16:10:54 +0800139 for (const auto& i : mapperResponse)
Lei YUad90ad52019-08-06 11:19:28 +0800140 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600141 services.emplace_back(i.first);
Lei YUad90ad52019-08-06 11:19:28 +0800142 }
Lei YUad90ad52019-08-06 11:19:28 +0800143 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600144 catch (const std::exception& e)
Lei YUad90ad52019-08-06 11:19:28 +0800145 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600146 throw std::runtime_error{
147 std::format("Unable to find services for path {}, interface {}: {}",
148 path, interface, e.what())};
Lei YUad90ad52019-08-06 11:19:28 +0800149 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600150 return services;
Lei YUad90ad52019-08-06 11:19:28 +0800151}
152
Lei YUf77189f2019-08-07 14:26:30 +0800153std::string Utils::getVersionId(const std::string& version) const
Lei YUad90ad52019-08-06 11:19:28 +0800154{
155 if (version.empty())
156 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600157 lg2::error("Error version is empty");
Lei YUad90ad52019-08-06 11:19:28 +0800158 return {};
159 }
160
George Liu0b7c7b32022-02-07 16:30:23 +0800161 using EVP_MD_CTX_Ptr =
162 std::unique_ptr<EVP_MD_CTX, decltype(&::EVP_MD_CTX_free)>;
163
164 std::array<unsigned char, EVP_MAX_MD_SIZE> digest{};
165 EVP_MD_CTX_Ptr ctx(EVP_MD_CTX_new(), &::EVP_MD_CTX_free);
166
167 EVP_DigestInit(ctx.get(), EVP_sha512());
168 EVP_DigestUpdate(ctx.get(), version.c_str(), strlen(version.c_str()));
169 EVP_DigestFinal(ctx.get(), digest.data(), nullptr);
Lei YUad90ad52019-08-06 11:19:28 +0800170
171 // Only need 8 hex digits.
George Liu0b7c7b32022-02-07 16:30:23 +0800172 char mdString[9];
173 snprintf(mdString, sizeof(mdString), "%02x%02x%02x%02x",
174 (unsigned int)digest[0], (unsigned int)digest[1],
175 (unsigned int)digest[2], (unsigned int)digest[3]);
176
177 return mdString;
Lei YUad90ad52019-08-06 11:19:28 +0800178}
179
Lei YU5f3584d2019-08-27 16:28:53 +0800180std::string Utils::getVersion(const std::string& inventoryPath) const
181{
Shawn McCarney487e2e12024-11-25 17:19:46 -0600182 std::string version;
183 try
184 {
185 // Invoke vendor-specific tool to get the version string, e.g.
186 // psutils --get-version
187 // /xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply0
188 auto [rc, output] = internal::exec(PSU_VERSION_UTIL, inventoryPath);
189 if (rc == 0)
190 {
191 version = output;
192 }
193 }
194 catch (const std::exception& e)
195 {
196 lg2::error("Unable to get firmware version for PSU {PSU}: {ERROR}",
197 "PSU", inventoryPath, "ERROR", e);
198 }
199 return version;
Lei YU5f3584d2019-08-27 16:28:53 +0800200}
201
Shawn McCarney783406e2024-11-17 21:49:37 -0600202std::string Utils::getModel(const std::string& inventoryPath) const
203{
Shawn McCarney487e2e12024-11-25 17:19:46 -0600204 std::string model;
205 try
206 {
207 // Invoke vendor-specific tool to get the model string, e.g.
208 // psutils --get-model
209 // /xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply0
210 auto [rc, output] = internal::exec(PSU_MODEL_UTIL, inventoryPath);
211 if (rc == 0)
212 {
213 model = output;
214 }
215 }
216 catch (const std::exception& e)
217 {
218 lg2::error("Unable to get model for PSU {PSU}: {ERROR}", "PSU",
219 inventoryPath, "ERROR", e);
220 }
221 return model;
Shawn McCarney783406e2024-11-17 21:49:37 -0600222}
223
Lei YU65207482019-10-11 16:39:36 +0800224std::string Utils::getLatestVersion(const std::set<std::string>& versions) const
225{
Shawn McCarney487e2e12024-11-25 17:19:46 -0600226 std::string latestVersion;
227 try
Lei YU65207482019-10-11 16:39:36 +0800228 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600229 if (!versions.empty())
230 {
231 std::stringstream args;
232 for (const auto& s : versions)
233 {
234 args << s << " ";
235 }
236 auto [rc, output] =
237 internal::exec(PSU_VERSION_COMPARE_UTIL, args.str());
238 if (rc == 0)
239 {
240 latestVersion = output;
241 }
242 }
Lei YU65207482019-10-11 16:39:36 +0800243 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600244 catch (const std::exception& e)
Lei YU65207482019-10-11 16:39:36 +0800245 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600246 lg2::error("Unable to get latest PSU firmware version: {ERROR}",
247 "ERROR", e);
Lei YU65207482019-10-11 16:39:36 +0800248 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600249 return latestVersion;
Lei YU65207482019-10-11 16:39:36 +0800250}
251
Lei YU4b9ac392019-10-12 11:02:26 +0800252bool Utils::isAssociated(const std::string& psuInventoryPath,
253 const AssociationList& assocs) const
254{
255 return std::find_if(assocs.begin(), assocs.end(),
256 [&psuInventoryPath](const auto& assoc) {
Patrick Williamsbab5ed92024-08-16 15:20:54 -0400257 return psuInventoryPath == std::get<2>(assoc);
258 }) != assocs.end();
Lei YU4b9ac392019-10-12 11:02:26 +0800259}
260
Patrick Williams374fae52022-07-22 19:26:55 -0500261any Utils::getPropertyImpl(sdbusplus::bus_t& bus, const char* service,
Lei YUf77189f2019-08-07 14:26:30 +0800262 const char* path, const char* interface,
263 const char* propertyName) const
264{
Shawn McCarney487e2e12024-11-25 17:19:46 -0600265 any anyValue{};
Lei YUf77189f2019-08-07 14:26:30 +0800266 try
267 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600268 auto method = bus.new_method_call(
269 service, path, "org.freedesktop.DBus.Properties", "Get");
270 method.append(interface, propertyName);
Lei YUf77189f2019-08-07 14:26:30 +0800271 auto reply = bus.call(method);
Shawn McCarney487e2e12024-11-25 17:19:46 -0600272 PropertyType value{};
Lei YUf77189f2019-08-07 14:26:30 +0800273 reply.read(value);
Shawn McCarney487e2e12024-11-25 17:19:46 -0600274 anyValue = value;
Lei YUf77189f2019-08-07 14:26:30 +0800275 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600276 catch (const std::exception& e)
Lei YUf77189f2019-08-07 14:26:30 +0800277 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600278 throw std::runtime_error{std::format(
279 "Unable to get property {} for path {} and interface {}: {}",
280 propertyName, path, interface, e.what())};
Lei YUf77189f2019-08-07 14:26:30 +0800281 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600282 return anyValue;
Lei YUf77189f2019-08-07 14:26:30 +0800283}
284
Lei YU5e0dcb32019-08-02 18:04:34 +0800285} // namespace utils