xref: /openbmc/entity-manager/src/entity_manager/utils.cpp (revision c5a2af90f201347fa94e8cc6f1aed3406dc985dd)
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         std::ranges::subrange<std::string::const_iterator> findStart =
95             iFindFirst(*strPtr, std::string_view(templateChar));
96 
97         if (!findStart)
98         {
99             break;
100         }
101 
102         std::ranges::subrange<std::string::iterator> searchRange(
103             strPtr->begin() + (findStart.end() - strPtr->begin()),
104             strPtr->end());
105         std::ranges::subrange<std::string::const_iterator> findSpace =
106             iFindFirst(searchRange, " ");
107 
108         std::string::const_iterator templateVarEnd;
109 
110         if (!findSpace)
111         {
112             // No space means the template var spans to the end of
113             // of the keyPair value
114             templateVarEnd = strPtr->end();
115         }
116         else
117         {
118             // A space marks the end of a template var
119             templateVarEnd = findSpace.begin();
120         }
121 
122         lg2::error(
123             "There's still template variable {VAR} un-replaced. Removing it from the string.\n",
124             "VAR", std::string(findStart.begin(), templateVarEnd));
125         strPtr->erase(findStart.begin(), templateVarEnd);
126     }
127 }
128 
129 // Replaces the template character like the other version of this function,
130 // but checks all properties on all interfaces provided to do the substitution
131 // with.
132 std::optional<std::string> templateCharReplace(
133     nlohmann::json::iterator& keyPair, const DBusObject& object,
134     const size_t index, const std::optional<std::string>& replaceStr)
135 {
136     for (const auto& [_, interface] : object)
137     {
138         auto ret = templateCharReplace(keyPair, interface, index, replaceStr);
139         if (ret)
140         {
141             handleLeftOverTemplateVars(keyPair);
142             return ret;
143         }
144     }
145     handleLeftOverTemplateVars(keyPair);
146     return std::nullopt;
147 }
148 
149 // finds the template character (currently set to $) and replaces the value with
150 // the field found in a dbus object i.e. $ADDRESS would get populated with the
151 // ADDRESS field from a object on dbus
152 std::optional<std::string> templateCharReplace(
153     nlohmann::json::iterator& keyPair, const DBusInterface& interface,
154     const size_t index, const std::optional<std::string>& replaceStr)
155 {
156     std::optional<std::string> ret = std::nullopt;
157 
158     if (keyPair.value().type() == nlohmann::json::value_t::object ||
159         keyPair.value().type() == nlohmann::json::value_t::array)
160     {
161         for (auto nextLayer = keyPair.value().begin();
162              nextLayer != keyPair.value().end(); nextLayer++)
163         {
164             templateCharReplace(nextLayer, interface, index, replaceStr);
165         }
166         return ret;
167     }
168 
169     std::string* strPtr = keyPair.value().get_ptr<std::string*>();
170     if (strPtr == nullptr)
171     {
172         return ret;
173     }
174 
175     boost::replace_all(*strPtr, std::string(templateChar) + "index",
176                        std::to_string(index));
177     if (replaceStr)
178     {
179         boost::replace_all(*strPtr, *replaceStr, std::to_string(index));
180     }
181 
182     for (const auto& [propName, propValue] : interface)
183     {
184         std::string templateName = templateChar + propName;
185         std::ranges::subrange<std::string::const_iterator> find =
186             iFindFirst(*strPtr, templateName);
187         if (!find)
188         {
189             continue;
190         }
191 
192         size_t start = find.begin() - strPtr->begin();
193 
194         // check for additional operations
195         if ((start == 0U) && find.end() == strPtr->end())
196         {
197             std::visit([&](auto&& val) { keyPair.value() = val; }, propValue);
198             return ret;
199         }
200 
201         constexpr const std::array<char, 5> mathChars = {'+', '-', '%', '*',
202                                                          '/'};
203         size_t nextItemIdx = start + templateName.size() + 1;
204 
205         if (nextItemIdx > strPtr->size() ||
206             std::find(mathChars.begin(), mathChars.end(),
207                       strPtr->at(nextItemIdx)) == mathChars.end())
208         {
209             std::string val = std::visit(VariantToStringVisitor(), propValue);
210             boost::ireplace_all(*strPtr, templateName, val);
211             continue;
212         }
213 
214         // save the prefix
215         std::string prefix = strPtr->substr(0, start);
216 
217         // operate on the rest
218         std::string end = strPtr->substr(nextItemIdx);
219 
220         std::vector<std::string> split;
221         boost::split(split, end, boost::is_any_of(" "));
222 
223         // need at least 1 operation and number
224         if (split.size() < 2)
225         {
226             lg2::error("Syntax error on template replacement of {STR}", "STR",
227                        *strPtr);
228             for (const std::string& data : split)
229             {
230                 lg2::error("{SPLIT} ", "SPLIT", data);
231             }
232             lg2::error("");
233             continue;
234         }
235 
236         // we assume that the replacement is a number, because we can
237         // only do math on numbers.. we might concatenate strings in the
238         // future, but thats later
239         int number = std::visit(VariantToIntVisitor(), propValue);
240         auto exprBegin = split.begin();
241         auto exprEnd = split.end();
242 
243         number = expression::evaluate(number, exprBegin, exprEnd);
244 
245         std::string replaced(find.begin(), find.end());
246         while (exprBegin != exprEnd)
247         {
248             replaced.append(" ").append(*exprBegin++);
249         }
250         ret = replaced;
251 
252         std::string result = prefix + std::to_string(number);
253         while (exprEnd != split.end())
254         {
255             result.append(" ").append(*exprEnd++);
256         }
257         keyPair.value() = result;
258 
259         // We probably just invalidated the pointer abovei,
260         // reset and continue to handle multiple templates
261         strPtr = keyPair.value().get_ptr<std::string*>();
262         if (strPtr == nullptr)
263         {
264             break;
265         }
266     }
267 
268     strPtr = keyPair.value().get_ptr<std::string*>();
269     if (strPtr == nullptr)
270     {
271         return ret;
272     }
273 
274     std::string_view strView = *strPtr;
275     int base = 10;
276     if (strView.starts_with("0x"))
277     {
278         strView.remove_prefix(2);
279         base = 16;
280     }
281 
282     uint64_t temp = 0;
283     bool fullMatch = false;
284     const std::from_chars_result res =
285         fromCharsWrapper(strView, temp, fullMatch, base);
286     if (res.ec == std::errc{} && fullMatch)
287     {
288         keyPair.value() = temp;
289     }
290 
291     return ret;
292 }
293 
294 std::string buildInventorySystemPath(std::string& boardName,
295                                      const std::string& boardType)
296 {
297     std::string path = "/xyz/openbmc_project/inventory/system/";
298     std::string boardTypeLower = boost::algorithm::to_lower_copy(boardType);
299 
300     std::regex_replace(boardName.begin(), boardName.begin(), boardName.end(),
301                        illegalDbusMemberRegex, "_");
302 
303     return std::format("{}{}/{}", path, boardTypeLower, boardName);
304 }
305 } // namespace em_utils
306