1 #include "config.h"
2 
3 #include "utils.hpp"
4 
5 #include <openssl/evp.h>
6 
7 #include <phosphor-logging/log.hpp>
8 
9 #include <algorithm>
10 #include <fstream>
11 #include <sstream>
12 
13 using namespace phosphor::logging;
14 
15 namespace utils
16 {
17 
18 namespace // anonymous
19 {
20 constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper";
21 constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper";
22 constexpr auto MAPPER_INTERFACE = "xyz.openbmc_project.ObjectMapper";
23 } // namespace
24 
25 namespace internal
26 {
27 template <typename... Ts>
concat_string(const Ts &...ts)28 std::string concat_string(const Ts&... ts)
29 {
30     std::stringstream s;
31     ((s << ts << " "), ...) << std::endl;
32     return s.str();
33 }
34 
35 // Helper function to run command
36 // Returns return code and the stdout
37 template <typename... Ts>
exec(const Ts &...ts)38 std::pair<int, std::string> exec(const Ts&... ts)
39 {
40     std::array<char, 512> buffer;
41     std::string cmd = concat_string(ts...);
42     std::stringstream result;
43     int rc;
44     FILE* pipe = popen(cmd.c_str(), "r");
45     if (!pipe)
46     {
47         throw std::runtime_error("popen() failed!");
48     }
49     while (fgets(buffer.data(), buffer.size(), pipe) != nullptr)
50     {
51         result << buffer.data();
52     }
53     rc = pclose(pipe);
54     return {rc, result.str()};
55 }
56 
57 } // namespace internal
getUtils()58 const UtilsInterface& getUtils()
59 {
60     static Utils utils;
61     return utils;
62 }
63 
getPSUInventoryPath(sdbusplus::bus_t & bus) const64 std::vector<std::string> Utils::getPSUInventoryPath(sdbusplus::bus_t& bus) const
65 {
66     std::vector<std::string> paths;
67     auto method = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
68                                       MAPPER_INTERFACE, "GetSubTreePaths");
69     method.append(PSU_INVENTORY_PATH_BASE);
70     method.append(0); // Depth 0 to search all
71     method.append(std::vector<std::string>({PSU_INVENTORY_IFACE}));
72     auto reply = bus.call(method);
73 
74     reply.read(paths);
75     return paths;
76 }
77 
getService(sdbusplus::bus_t & bus,const char * path,const char * interface) const78 std::string Utils::getService(sdbusplus::bus_t& bus, const char* path,
79                               const char* interface) const
80 {
81     auto services = getServices(bus, path, interface);
82     if (services.empty())
83     {
84         return {};
85     }
86     return services[0];
87 }
88 
getServices(sdbusplus::bus_t & bus,const char * path,const char * interface) const89 std::vector<std::string> Utils::getServices(sdbusplus::bus_t& bus,
90                                             const char* path,
91                                             const char* interface) const
92 {
93     auto mapper = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
94                                       MAPPER_INTERFACE, "GetObject");
95 
96     mapper.append(path, std::vector<std::string>({interface}));
97     try
98     {
99         auto mapperResponseMsg = bus.call(mapper);
100 
101         std::vector<std::pair<std::string, std::vector<std::string>>>
102             mapperResponse;
103         mapperResponseMsg.read(mapperResponse);
104         if (mapperResponse.empty())
105         {
106             log<level::ERR>("Error reading mapper response");
107             throw std::runtime_error("Error reading mapper response");
108         }
109         std::vector<std::string> ret;
110         for (const auto& i : mapperResponse)
111         {
112             ret.emplace_back(i.first);
113         }
114         return ret;
115     }
116     catch (const sdbusplus::exception_t& ex)
117     {
118         log<level::ERR>("GetObject call failed", entry("PATH=%s", path),
119                         entry("INTERFACE=%s", interface));
120         throw std::runtime_error("GetObject call failed");
121     }
122 }
123 
getVersionId(const std::string & version) const124 std::string Utils::getVersionId(const std::string& version) const
125 {
126     if (version.empty())
127     {
128         log<level::ERR>("Error version is empty");
129         return {};
130     }
131 
132     using EVP_MD_CTX_Ptr =
133         std::unique_ptr<EVP_MD_CTX, decltype(&::EVP_MD_CTX_free)>;
134 
135     std::array<unsigned char, EVP_MAX_MD_SIZE> digest{};
136     EVP_MD_CTX_Ptr ctx(EVP_MD_CTX_new(), &::EVP_MD_CTX_free);
137 
138     EVP_DigestInit(ctx.get(), EVP_sha512());
139     EVP_DigestUpdate(ctx.get(), version.c_str(), strlen(version.c_str()));
140     EVP_DigestFinal(ctx.get(), digest.data(), nullptr);
141 
142     // Only need 8 hex digits.
143     char mdString[9];
144     snprintf(mdString, sizeof(mdString), "%02x%02x%02x%02x",
145              (unsigned int)digest[0], (unsigned int)digest[1],
146              (unsigned int)digest[2], (unsigned int)digest[3]);
147 
148     return mdString;
149 }
150 
getVersion(const std::string & inventoryPath) const151 std::string Utils::getVersion(const std::string& inventoryPath) const
152 {
153     // Invoke vendor-specify tool to get the version string, e.g.
154     //   psutils get-version
155     //   /xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply0
156     auto [rc, r] = internal::exec(PSU_VERSION_UTIL, inventoryPath);
157     return (rc == 0) ? r : "";
158 }
159 
getLatestVersion(const std::set<std::string> & versions) const160 std::string Utils::getLatestVersion(const std::set<std::string>& versions) const
161 {
162     if (versions.empty())
163     {
164         return {};
165     }
166     std::stringstream args;
167     for (const auto& s : versions)
168     {
169         args << s << " ";
170     }
171     auto [rc, r] = internal::exec(PSU_VERSION_COMPARE_UTIL, args.str());
172     return (rc == 0) ? r : "";
173 }
174 
isAssociated(const std::string & psuInventoryPath,const AssociationList & assocs) const175 bool Utils::isAssociated(const std::string& psuInventoryPath,
176                          const AssociationList& assocs) const
177 {
178     return std::find_if(assocs.begin(), assocs.end(),
179                         [&psuInventoryPath](const auto& assoc) {
180         return psuInventoryPath == std::get<2>(assoc);
181     }) != assocs.end();
182 }
183 
getPropertyImpl(sdbusplus::bus_t & bus,const char * service,const char * path,const char * interface,const char * propertyName) const184 any Utils::getPropertyImpl(sdbusplus::bus_t& bus, const char* service,
185                            const char* path, const char* interface,
186                            const char* propertyName) const
187 {
188     auto method = bus.new_method_call(service, path,
189                                       "org.freedesktop.DBus.Properties", "Get");
190     method.append(interface, propertyName);
191     try
192     {
193         PropertyType value{};
194         auto reply = bus.call(method);
195         reply.read(value);
196         return any(value);
197     }
198     catch (const sdbusplus::exception_t& ex)
199     {
200         log<level::ERR>("GetProperty call failed", entry("PATH=%s", path),
201                         entry("INTERFACE=%s", interface),
202                         entry("PROPERTY=%s", propertyName));
203         throw std::runtime_error("GetProperty call failed");
204     }
205 }
206 
207 } // namespace utils
208