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