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