xref: /openbmc/entity-manager/src/entity_manager/utils.cpp (revision ce8d1d0df3f40a48f8e5937ec21568bf0a435969)
1 #include "utils.hpp"
2 
3 #include "../utils.hpp"
4 #include "../variant_visitors.hpp"
5 #include "expression.hpp"
6 #include "phosphor-logging/lg2.hpp"
7 
8 #include <boost/algorithm/string/case_conv.hpp>
9 #include <boost/algorithm/string/classification.hpp>
10 #include <boost/algorithm/string/replace.hpp>
11 #include <boost/algorithm/string/split.hpp>
12 #include <phosphor-logging/lg2.hpp>
13 #include <sdbusplus/bus/match.hpp>
14 
15 #include <fstream>
16 #include <regex>
17 
18 const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]");
19 
20 namespace em_utils
21 {
22 
23 constexpr const char* templateChar = "$";
24 
25 bool fwVersionIsSame()
26 {
27     std::ifstream version(versionFile);
28     if (!version.good())
29     {
30         lg2::error("Can't read {PATH}", "PATH", versionFile);
31         return false;
32     }
33 
34     std::string versionData;
35     std::string line;
36     while (std::getline(version, line))
37     {
38         versionData += line;
39     }
40 
41     std::string expectedHash =
42         std::to_string(std::hash<std::string>{}(versionData));
43 
44     std::error_code ec;
45     std::filesystem::create_directory(configurationOutDir, ec);
46 
47     if (ec)
48     {
49         lg2::error("could not create directory {DIR}", "DIR",
50                    configurationOutDir);
51         return false;
52     }
53 
54     std::ifstream hashFile(versionHashFile);
55     if (hashFile.good())
56     {
57         std::string hashString;
58         hashFile >> hashString;
59 
60         if (expectedHash == hashString)
61         {
62             return true;
63         }
64         hashFile.close();
65     }
66 
67     std::ofstream output(versionHashFile);
68     output << expectedHash;
69     return false;
70 }
71 
72 void handleLeftOverTemplateVars(nlohmann::json::iterator& keyPair)
73 {
74     if (keyPair.value().type() == nlohmann::json::value_t::object ||
75         keyPair.value().type() == nlohmann::json::value_t::array)
76     {
77         for (auto nextLayer = keyPair.value().begin();
78              nextLayer != keyPair.value().end(); nextLayer++)
79         {
80             handleLeftOverTemplateVars(nextLayer);
81         }
82         return;
83     }
84 
85     std::string* strPtr = keyPair.value().get_ptr<std::string*>();
86     if (strPtr == nullptr)
87     {
88         return;
89     }
90 
91     // Walking through the string to find $<templateVar>
92     while (true)
93     {
94         auto [firstIndex, lastIndex] = iFindFirst(*strPtr, templateChar);
95         if (firstIndex == std::string_view::npos)
96         {
97             break;
98         }
99 
100         size_t templateVarEndIndex = 0;
101         auto [firstSpaceIndex, _] = iFindFirst(strPtr->substr(lastIndex), " ");
102         if (firstSpaceIndex == std::string_view::npos)
103         {
104             // No space means the template var spans to the end of
105             // of the keyPair value
106             templateVarEndIndex = strPtr->size();
107         }
108         else
109         {
110             // A space marks the end of a template var
111             templateVarEndIndex = lastIndex + firstSpaceIndex;
112         }
113 
114         lg2::error(
115             "There's still template variable {VAR} un-replaced. Removing it from the string.\n",
116             "VAR",
117             strPtr->substr(firstIndex, templateVarEndIndex - firstIndex));
118         strPtr->erase(firstIndex, templateVarEndIndex - firstIndex);
119     }
120 }
121 
122 // Replaces the template character like the other version of this function,
123 // but checks all properties on all interfaces provided to do the substitution
124 // with.
125 std::optional<std::string> templateCharReplace(
126     nlohmann::json::iterator& keyPair, const DBusObject& object,
127     const size_t index, const std::optional<std::string>& replaceStr)
128 {
129     for (const auto& [_, interface] : object)
130     {
131         auto ret = templateCharReplace(keyPair, interface, index, replaceStr);
132         if (ret)
133         {
134             handleLeftOverTemplateVars(keyPair);
135             return ret;
136         }
137     }
138     handleLeftOverTemplateVars(keyPair);
139     return std::nullopt;
140 }
141 
142 // finds the template character (currently set to $) and replaces the value with
143 // the field found in a dbus object i.e. $ADDRESS would get populated with the
144 // ADDRESS field from a object on dbus
145 std::optional<std::string> templateCharReplace(
146     nlohmann::json::iterator& keyPair, const DBusInterface& interface,
147     const size_t index, const std::optional<std::string>& replaceStr)
148 {
149     std::optional<std::string> ret = std::nullopt;
150 
151     if (keyPair.value().type() == nlohmann::json::value_t::object ||
152         keyPair.value().type() == nlohmann::json::value_t::array)
153     {
154         for (auto nextLayer = keyPair.value().begin();
155              nextLayer != keyPair.value().end(); nextLayer++)
156         {
157             templateCharReplace(nextLayer, interface, index, replaceStr);
158         }
159         return ret;
160     }
161 
162     std::string* strPtr = keyPair.value().get_ptr<std::string*>();
163     if (strPtr == nullptr)
164     {
165         return ret;
166     }
167 
168     boost::replace_all(*strPtr, std::string(templateChar) + "index",
169                        std::to_string(index));
170     if (replaceStr)
171     {
172         boost::replace_all(*strPtr, *replaceStr, std::to_string(index));
173     }
174 
175     for (const auto& [propName, propValue] : interface)
176     {
177         std::string templateName = templateChar + propName;
178         auto [start, endIdx] = iFindFirst(*strPtr, templateName);
179         if (start == std::string::npos)
180         {
181             continue;
182         }
183 
184         // check for additional operations
185         if ((start == 0U) && endIdx == strPtr->size())
186         {
187             std::visit([&](auto&& val) { keyPair.value() = val; }, propValue);
188             return ret;
189         }
190 
191         constexpr const std::array<char, 5> mathChars = {'+', '-', '%', '*',
192                                                          '/'};
193         size_t nextItemIdx = start + templateName.size() + 1;
194 
195         if (nextItemIdx > strPtr->size() ||
196             std::find(mathChars.begin(), mathChars.end(),
197                       strPtr->at(nextItemIdx)) == mathChars.end())
198         {
199             std::string val = std::visit(VariantToStringVisitor(), propValue);
200             boost::ireplace_all(*strPtr, templateName, val);
201             continue;
202         }
203 
204         // save the prefix
205         std::string prefix = strPtr->substr(0, start);
206 
207         // operate on the rest
208         std::string end = strPtr->substr(nextItemIdx);
209 
210         std::vector<std::string> split;
211         boost::split(split, end, boost::is_any_of(" "));
212 
213         // need at least 1 operation and number
214         if (split.size() < 2)
215         {
216             lg2::error("Syntax error on template replacement of {STR}", "STR",
217                        *strPtr);
218             for (const std::string& data : split)
219             {
220                 lg2::error("{SPLIT} ", "SPLIT", data);
221             }
222             lg2::error("");
223             continue;
224         }
225 
226         // we assume that the replacement is a number, because we can
227         // only do math on numbers.. we might concatenate strings in the
228         // future, but thats later
229         int number = std::visit(VariantToIntVisitor(), propValue);
230         auto exprBegin = split.begin();
231         auto exprEnd = split.end();
232 
233         number = expression::evaluate(number, exprBegin, exprEnd);
234 
235         std::string replaced(strPtr->begin() + start, strPtr->begin() + endIdx);
236         while (exprBegin != exprEnd)
237         {
238             replaced.append(" ").append(*exprBegin++);
239         }
240         ret = replaced;
241 
242         std::string result = prefix + std::to_string(number);
243         while (exprEnd != split.end())
244         {
245             result.append(" ").append(*exprEnd++);
246         }
247         keyPair.value() = result;
248 
249         // We probably just invalidated the pointer abovei,
250         // reset and continue to handle multiple templates
251         strPtr = keyPair.value().get_ptr<std::string*>();
252         if (strPtr == nullptr)
253         {
254             break;
255         }
256     }
257 
258     strPtr = keyPair.value().get_ptr<std::string*>();
259     if (strPtr == nullptr)
260     {
261         return ret;
262     }
263 
264     std::string_view strView = *strPtr;
265     int base = 10;
266     if (strView.starts_with("0x"))
267     {
268         strView.remove_prefix(2);
269         base = 16;
270     }
271 
272     uint64_t temp = 0;
273     bool fullMatch = false;
274     const std::from_chars_result res =
275         fromCharsWrapper(strView, temp, fullMatch, base);
276     if (res.ec == std::errc{} && fullMatch)
277     {
278         keyPair.value() = temp;
279     }
280 
281     return ret;
282 }
283 
284 std::string buildInventorySystemPath(std::string& boardName,
285                                      const std::string& boardType)
286 {
287     std::string path = "/xyz/openbmc_project/inventory/system/";
288     std::string boardTypeLower = boost::algorithm::to_lower_copy(boardType);
289 
290     std::regex_replace(boardName.begin(), boardName.begin(), boardName.end(),
291                        illegalDbusMemberRegex, "_");
292 
293     return std::format("{}{}/{}", path, boardTypeLower, boardName);
294 }
295 } // namespace em_utils
296