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