xref: /openbmc/phosphor-psu-code-mgmt/src/utils.cpp (revision 638b84ae544defb76091053652042e19adfd2f53)
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         std::vector<std::pair<std::string, std::vector<std::string>>>
136             mapperResponse;
137         mapperResponseMsg.read(mapperResponse);
138         services.reserve(mapperResponse.size());
139         for (const auto& i : mapperResponse)
140         {
141             services.emplace_back(i.first);
142         }
143     }
144     catch (const std::exception& e)
145     {
146         throw std::runtime_error{
147             std::format("Unable to find services for path {}, interface {}: {}",
148                         path, interface, e.what())};
149     }
150     return services;
151 }
152 
getVersionId(const std::string & version) const153 std::string Utils::getVersionId(const std::string& version) const
154 {
155     if (version.empty())
156     {
157         lg2::error("Error version is empty");
158         return {};
159     }
160 
161     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);
170 
171     // Only need 8 hex digits.
172     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;
178 }
179 
getVersion(const std::string & inventoryPath) const180 std::string Utils::getVersion(const std::string& inventoryPath) const
181 {
182     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;
200 }
201 
getModel(const std::string & inventoryPath) const202 std::string Utils::getModel(const std::string& inventoryPath) const
203 {
204     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;
222 }
223 
getLatestVersion(const std::set<std::string> & versions) const224 std::string Utils::getLatestVersion(const std::set<std::string>& versions) const
225 {
226     std::string latestVersion;
227     try
228     {
229         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         }
243     }
244     catch (const std::exception& e)
245     {
246         lg2::error("Unable to get latest PSU firmware version: {ERROR}",
247                    "ERROR", e);
248     }
249     return latestVersion;
250 }
251 
isAssociated(const std::string & psuInventoryPath,const AssociationList & assocs) const252 bool 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) {
257                             return psuInventoryPath == std::get<2>(assoc);
258                         }) != assocs.end();
259 }
260 
getPropertyImpl(sdbusplus::bus_t & bus,const char * service,const char * path,const char * interface,const char * propertyName) const261 any Utils::getPropertyImpl(sdbusplus::bus_t& bus, const char* service,
262                            const char* path, const char* interface,
263                            const char* propertyName) const
264 {
265     any anyValue{};
266     try
267     {
268         auto method = bus.new_method_call(
269             service, path, "org.freedesktop.DBus.Properties", "Get");
270         method.append(interface, propertyName);
271         auto reply = bus.call(method);
272         PropertyType value{};
273         reply.read(value);
274         anyValue = value;
275     }
276     catch (const std::exception& e)
277     {
278         throw std::runtime_error{std::format(
279             "Unable to get property {} for path {} and interface {}: {}",
280             propertyName, path, interface, e.what())};
281     }
282     return anyValue;
283 }
284 
285 } // namespace utils
286